数组中数字出现的次数(剑指offer-56)
题目描述如下:

思路分析:
实际上,抛开题目复杂度的要求,此题有多种解法。但由于对复杂度有要求,因此本题的解法就很受限制了。官方题解给了利用按位异或、相与操作来解此题。但,觉得官方题解讲解得实在难以看懂。遂另作此篇文章以图能说明清楚官方题解的方法。
1.题解用到的技巧
这道题解主要是利用了以下技巧:
- a⊕a=0,a是一个整数a\oplus a=0,a是一个整数a⊕a=0,a是一个整数;
- 0⊕a=a,a是一个整数0 \oplus a = a,a是一个整数0⊕a=a,a是一个整数
- 如果a和b是不同的整数,那么a⊕ba \oplus ba⊕b的二进制至少有一位为1。
- 异或运算的交换律:(a⊕b)⊕c=a⊕(b⊕c)异或运算的交换律:(a\oplus b) \oplus c=a\oplus (b \oplus c)异或运算的交换律:(a⊕b)⊕c=a⊕(b⊕c)
先来看此问题另一个相似的问题:假如一个整型数组 nums 里除一个数字之外,其他数字都出现了两次。要怎样找到这个只出现一次的数字?
解法:我们可以通过让所有数字进行异或操作,最后的异或结果就是这个只出现一位的数字。例如,nums={1,1,3,4,3,4,5}。那么有5=1⊕1⊕1⊕3⊕4⊕3⊕4⊕5=1⊕1⊕3⊕3⊕4⊕4⊕5=0⊕5=55 = 1 \oplus 1 \oplus 1 \oplus 3 \oplus 4 \oplus 3 \oplus 4 \oplus 5=1 \oplus 1 \oplus 3 \oplus 3 \oplus 4 \oplus 4 \oplus 5=0 \oplus 5=55=1⊕1⊕1⊕3⊕4⊕3⊕4⊕5=1⊕1⊕3⊕3⊕4⊕4⊕5=0⊕5=5。
参照上面这个思路,如果我们把原数组“分成”两组(假设a和b是原数组中只出现一次的两个数字),且这两组满足以下条件:
1. 第一组:数字a和原数组中部分出现两次的数字在一组;
2. 第二组:数字b和原数组中除第一组外出现两次的数字在一组;
这样我们通过分别对两组的所有数字做异或运算,就可分别得到a和b。
2.思想:把原数组分为两组
问题是如何把原数组“分为”两组?利用a和b二进制表示法中不同的那一位。
且看一个例子:nums = {2,3,2,3,1,6},1和6的二进制分别是 0001b 和 0110b ,1⊕6=7=0111b1 \oplus 6 = 7=0111b1⊕6=7=0111b。现在 a 和 b 不同的二进制位有三位,我们只取一位即可,假设取最低位的1,记为m=1。
然后,要怎么利用 a 和 b 不同的这一个二进制位把原数组分组呢?把原数组中的每个数字与 m 做相与操作。如果 nums[i]&m=1nums[i]\&m = 1nums[i]&m=1,则划分为一组;否则,划分为另一组:
- 2&m=2&1=0010&0001=0001=02 \& m=2 \& 1=0010\&0001=0001=02&m=2&1=0010&0001=0001=0
- 3&m=3&1=0011&0001=0001=13 \& m=3 \& 1=0011\&0001=0001=13&m=3&1=0011&0001=0001=1
- 2&m=2&1=0010&0001=0001=02 \& m=2 \& 1=0010\&0001=0001=02&m=2&1=0010&0001=0001=0
- 3&m=3&1=0011&0001=0001=13 \& m=3 \& 1=0011\&0001=0001=13&m=3&1=0011&0001=0001=1
- 1&m=1&1=0001&0001=0001=11 \& m=1 \& 1=0001\&0001=0001=11&m=1&1=0001&0001=0001=1
- 6&m=6&1=0110&0001=0001=06 \& m=6 \& 1=0110\&0001=0001=06&m=6&1=0110&0001=0001=0
所以最后是两组:{2,2,6} 和 {3,3,1}。分组的问题解决了,问题是这个a和b二进制表示法中不同的那一位怎么求?就用如果a和b是不同的整数,那么a⊕ba \oplus ba⊕b的二进制至少有一位为 1这个结论,我们就从a⊕ba \oplus ba⊕b取出它们二进制表示法中不同的那一位。
3.算法流程
综上,最终算法的流程如下:
1. 求出原数组所有数字的异或值,得出a⊕ba \oplus ba⊕b。
2. 从 a⊕ba \oplus ba⊕b 得出a和b二进制表示法中不同的一位(可能有多个二进制位不同,取一个即可)。
3. 把原数组分为两组,分组对两组的所有数字求异或,得出最终答案。
最后就是写代码了,语言版本较多,我就只列出C++版本的了。
4.代码
class Solution {
public:
vector<int> singleNumbers(vector<int>& nums) {
int a = 0;
int b = 0;
int aXorb = 0;
int diffBit = 1;
for ( auto num : nums)
aXorb ^= num;
while ( (aXorb & diffBit) == 0 )
diffBit <<= 1;
for ( auto num : nums ) {
if ( diffBit & num )
a ^= num;
else
b ^= num;
}
return vector<int> {a, b};
}
};
如有错误之处,请各位码友批评指正。创作不易,引用请注明出处,顺便可以再点一个赞,感激不尽。
博客介绍了如何利用位运算中的异或操作解决数组中两个唯一出现一次的数字问题。首先,通过异或所有数组元素找出这两个数字的异或结果,然后确定它们二进制表示中不同的位,以此位为界限将数组分成两组,分别对两组进行异或操作,最终得到两个不同的数字。算法流程包括:计算异或值、确定分组依据、分组并异或。代码实现以C++展示。
2170

被折叠的 条评论
为什么被折叠?



