题目
题目链接:剑指 Offer 56 - I. 数组中数字出现的次数
实现思路:
首先我们知道这道题要找两个不同的数字,除了这两个不同的数字,其他数字都是两两配对的。
我们做过一道题:消失的数字,在这道题中,我们可以很轻松的通过异或找到其中一个不在的数字;我们使用的方式是 相同的数字异或会得到零的方式找到的,而0异或任何数,都不会变;这道题难点在于有两个不相同的数;
比如要找一个数组nums[1,1,2,3,3]找到其中的只有一个不同的数字2,我们可以通过 0 ^ 1 ^ 1 ^ 3 ^ 3 ^ 2 得到的结果就是2;那么假如一个数组有两个不同的数字,其他都是两两配对相同的呢?我们就无法直接通过异或得到;
所以我们借助上面的思想:拆分:把有两个不同的数字的数组,分别拆分两个数组,这两个数组,只包含一个不同的数字;
比如:nums[1,1,2,3,4,4,5,5] :拆分成:0 ^ 1 ^ 1 ^ 2 和 0 ^ 4 ^4 ^5 ^5 ^ 3; 那么这样我们就可以找出 2 和 3 啦!(上面的拆分的组合不一定是我的举例的方式,我这里重点关注是拆分,而不是拆分对,要拆分对,请继续看如何拆分的步骤)
现在的要完成是如何拆分:假如我们设两个不同的数为 a ,b ,那么 a ^ b 的二进制位中的某一个位肯定是1(至少有一个二进制位为1),因为异或的性质告诉我们 不同的数字异或为1,知道这个有什么用呢?知道这个我们可以找到这个为1的位置具体在哪里,从而通过它的位置来区分数组中数字的分组
那么是如何通过a^b的二进制中的1位置来确认分组呢?
其实我们只要用这个1的位置来和nums数组中的每个数字按位与一下,这样就可以区分开来了!
代码实现
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* singleNumbers(int* nums, int numsSize, int* returnSize){
//开辟存放结果的数组
int* retNums = (int*)malloc(sizeof(int)*2);
int x = 0; //保存两个不相同数字的异或结果
//比如:nums = [4,1,4,6]
//则 x = 1^6;
for(int i = 0; i< numsSize;i++){
x ^= nums[i];
}
//找出两个不同数字异或结果x的二进制中为位数1的位置
int m = 1; //临时变量,得到数为二进制中位数为1的数
/比如:x = 2^6 = 010^110 = 100
// x & m = 100 & 001 = 000;
// x & m = 100 & 010 = 000;
// x & m = 100 & 100 = 100; 此时 x &m != 0; 退出循环
//此时m = 100; 而m二进制中位数为1的位置在最高位
//(这个位置是由数据决定的,我们只需要找出m从左到右第一个为1的就行)
while((x & m )== 0) m <<= 1;
//找到x中二进制为1的位置后,遍历数组与其m 按位与
//假如x中为1的位置与m与为0,则表示是不同数的其中一个;
//假如不为0,即为1,那么就是不同数的另外一个
int a = 0;
int b = 0;
for(int i = 0;i<numsSize;i++){
if((nums[i] & m )== 0)
a ^=nums[i];
else
b ^=nums[i];
}
retNums[0] = a;
retNums[1] = b;
*returnSize=2;
return retNums ;
}