用异或^可解此题。
但是首先要知道一个知识点,a ^ b ^ a = a ^ a ^ b = b ^ a ^ a = b,这个知识点也就是本题的简单版本:如果数组中除了某一个数字,其他数字都出现了两次,找出该数字。思路就是遍历数组,对每一个数字都求异或,最后得到的值就是要找的数字。
有了该知识点的储备,再来看看本题。本题是要找两个数字a和b,那我们把该数组分成两个数组,其中a和一部分出现两次的数字在一块儿,b和另一部分出现两次的数字在一块儿,那这两个数组不是就变成了上面讲的那个简单版本中的数组吗?然后再分别对这两个数组求异或,就可以找到这两个数字了。
举例:[a,2,2,3,3,b]。把该数组分成[a,2,2]和[3,3,b],再对这两个数组求异或,便能得到a和b。
问题:怎么把a和b区分开来?
可以利用二进制来区分。先对整个数组求异或得到c,根据上面的知识,可以知道c其实就是a ^ b = c。那么对于c,假如c二进制的第一位是1,其实就代表a二进制的第一位是1(或0),b二进制的第一位是0(或1),总而言之如果第一位的c等于1,那么a和b在第一位肯定不相等。
所以我们就可以想到利用二进制的第一位(有可能是第二位,第三位 。。。因为上面是假设的c第一位是1)为1来区分两个数组,第一位为1的是数组一,第一位为0的是数组二。这样就相当于把a和b给区分开来了。
a和b区分开以后,剩下的就简单了,判断数组中其他数字的二进制的第一位是否为1,是的话就分到数组一,为0就分到数组二。最后对数组一和数组二分别进行异或,得到的就是a和b。
有个地方没有讲清楚,利用二进制的第一位为1来区分两个数组,如果第一位不是1,那就判断第二位,第三位,一直到找到为1的地方。假设一直找到第n位才为1,那就判断数组中的其他数字的二进制的第n位是否为1,做&运算即可判断。
位运算感觉还是挺抽象的,我也是用具体的例子来照着别人的代码推一遍才搞明白,为什么那个地方做&运算就可以得到它,为什么那个地方做^运算又可以得到它。要是还没看懂的小伙伴也可以自己写一个具体的数组,然后照着代码先在草稿纸上推一遍,多搞几次就明白了。
代码如下:
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param array int整型vector
* @return int整型vector
*/
vector<int> FindNumsAppearOnce(vector<int>& array) {
vector<int> v;
int res = 0; //先对整个数组求异或
for(int i = 0; i < array.size(); i++) res ^= array[i];
int cmp = 1;
while((cmp & res) == 0) cmp <<= 1; //一直找到为1的二进制位
int a = 0, b = 0;
for(int i = 0; i < array.size(); i++){
if((cmp & array[i]) == 0) a ^= array[i];
else b ^= array[i];
}
if(a > b) swap(a, b);
v.push_back(a);
v.push_back(b);
return v;
}
};