题目:一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
思路:如果一个数组中一个数字只出现一次,其它数字出现两次,找到只出现一次的数字,这个问题比较好实现,利用异或运算的特点(任何一个数字异或它自己都等于0),如果我们从头到尾依次异或数组中的每个数字,那么最终的结果刚好是那个只出现一次的数字,因为那些成对出现两次的数字全部在异或中抵消了。因此该题的主要难点是如何把给定数组转化分解为满足此条件的数组。因此解题过程可以分为两步:
第一步——把原数组分为两个子数组,使得每个子数组包含一个只出现一次的数字,而其他数字都成对出现两次
从头到尾依次异或数组中的每个数字,在结果数字中找到第一个为1的位的位置,记为第n位(indexOf1),并以第n位是不是1为标准把原数组中的数字分为两个子数组,第一个数组中每个数字的第n位都是1,第二个数组中每个数字的第n位都是0。
第二步——依次异或子数组中的每个数字,最终的结果刚好为只出现一次的数字
分别依次异或两个子数组,最后两个子数组数组的异或结果就是那两个只出现一次的数字。
核心代码如下:
unsigned int FindFirstBitIs1(int num);
bool IsBit1(int num, unsigned int indexBit);
void FindNumsAppearOnce(int data[], int length, int* num1, int* num2){
if(data == nullptr || length < 2)
return;
int result = 0;
for(int i = 0; i < length; i++)
result ^= data[i]; //从头到尾依次异或数组中的每个数字,得到两个只出现一次的数字的异或结果
unsigned int indexOf1 = FindFirstBitIs1(result);
*num1 = *num2 = 0; //*num1,*num2最终存放数组中只出现一次的两个数字
for(int j = 0; j < length; j++){
if(IsBit1(data[j], indexOf1))
//data[j]从右边数的第indexOf1位为1
*num1 ^= data[j];
else
//data[j]从右边数的第indexOf1位不为1
*num2 ^= data[j];
}
}
//用来在整数num的二进制表示中找到最右边是1的位,例如num = 2,indexBit = 1
unsigned int FindFirstBitIs1(int num){
int indexBit = 0;
while(((num & 1) == 0) && (indexBit < 8 * sizeof(int))){ 二进制表示的位数长度最大为32位
num = num >> 1;
indexBit++;
}
return indexBit;
}
//判断在num的二进制表示中从右边数起的indexBit位是不是1
bool IsBit1(int num, unsigned int indexBit){
num = num >> indexBit;
return (num & 1);
}