问题:一个整数数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度为O(n),空间复杂度为O(1)。
分析:这是一个很新颖的关于位运算的题目。
首先考虑这个问题的一个简单版本:一个整数数组里除了一个数字之外,其他的数字都出现两次,请写程序找出这个只出现一次的数字。
这个问题的突破口在哪?题目中数组的性质是只有一个整数出现一次,其他的都出现两次。这样的话就使我们想到了异或运算的性质:任何一个数字异或它自己都等于0。也就是说如果从头到尾依次异或数组中的每一个整数,那么最终的结果刚好是那个只出现一次的数字。因为那么两两相同的整数在异或的过程中等于0抵消了。
根据上面的这个简单为题的解决方案,回到题目中的问题。如果能把原数组分为两个子数组,在每个子数组中包含一个只出现一次的数字,而其他的数字都出现两次。如果能够这样拆分原数组,那么问题就变成在拆分的两个数组中分别寻找这两个只出现一次的数字。
那么剩下的问题就是根据什么条件拆分数组?解决的办法是:把所有的整数依次异或运算,因为其他的有配对的整数异或的结果等于0,这样最后异或的结果就相当于把这两个只出现一次的整数异或,所以想要区分这两个整数,可以从它们异或的结果上区分。在它们异或的结果中找一个为1的bit位,即为第N位。根据异或的运算原理,那么这两个数的第N位肯定一个为1一个为0。根据第N位是1或者0把整个数组分成两组。就能满足每个子数组包含一个只出现一次的整数,其他的数字两两配对的出现在不同的子数组中。
算法步骤:
(1)依次异或数组中的每一个整数,得出异或运算的结果。
(2)找到异或运算结果的二进制形式中,最低位的1对应的下标值。把这个位作为区分两个整数的标准
(3)根据区分标准,把整个数组分成两个子数组,分别求它们的异或运算值,得到两个运算结果就是要求的两个只出现一次的整数。
函数声明:
bool FindTwoSingleNums(int nums[], int nLength, int &a, int &b);
bool IsBit1(int num, uint indexBit);
uint FindFirstBitIs1(int num);
代码实现:
#include <stdio.h> #include <stdlib.h> #include <assert.h> #define uint unsigned int bool FindTwoSingleNums(int nums[], int nLength, int &a, int &b); bool IsBit1(int num, uint indexBit); uint FindFirstBitIs1(int num); ///<summary> ///在一个数组中找不能配对的两个整数 ///<param name=num>数组名</param> ///<param name=nLength>数组长度</param> ///<param name=a>第一个不能配对的整数引用</param> ///<param name=b>第二个不能配对的整数引用</param> ///<returns>返回bool表示查找是否成功</return> ///</summary> bool FindTwoSingleNums(int nums[], int nLength, int &a, int &b) { if(nLength < 2) { return false; } int resXOR = 0; for(int i = 0; i < nLength; i++) { resXOR ^= nums[i]; }//for uint index = FindFirstBitIs1(resXOR); for(int i = 0; i < nLength; i++) { if(IsBit1(nums[i], index)) { a ^= nums[i]; } else { b ^= nums[i]; } }//for }//FindTwoSingleNums() ///<summary> ///找出一个整数对应的二进制数出现1的最低位的下标 ///<param name=num>整数</param> ///<returns>出现1的位的下标值</return> ///</summary> uint FindFirstBitIs1(int num) { assert(num != 0); uint index = 0; uint maxIndex = sizeof(num) * 8; //注意运算符的优先级 while(((num & 1) == 0) && (index < maxIndex)) { num >>= 1; ++index; }//while return index; }//FindFirstBitIs1() ///<summary> ///判断一个整数对应二进制的某一位是否为1 ///<param name=num>整数</param> ///<param name=indexBit>要判断的位的下标</param> ///<returns>返回判断的结果</return> ///</summary> bool IsBit1(int num, uint indexBit) { num >>= indexBit; return num & 1; }//IsBit1()
测试代码:
void main()
{
int array[16] = {51,52,78,45,89,54,12,70,
51,52,78,45,89,54,12,74};
//初始化为0,否则就会是一个随机数和数组异或运算
//0和任何数异或运算都等于其本身
int oneSingleNum = 0;
int twoSingleNum = 0;
FindTwoSingleNums(array, 16, oneSingleNum, twoSingleNum);
printf("%d--%d\n", oneSingleNum, twoSingleNum);
system("pause");
}
PS:关键词—>相同数字抑或结果为0,并根据所有元素异或结果第一个出现1的位置(出现1的最低位)把数组分成两部分。