题目:一个整型数组里除了两个数字外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度为O(N),空间复杂度为O(1)。
例如输入数组{2,4,3,6,3,2,5,5},因为只有4和6在这个数组里出现了一次,所以最后输出的是4,6。
分析:因为题目要求的时间复杂度和空间复杂度分别为:O(N)和O(1)。所以这个题目不能借助辅助空间,那么也就是要在一次遍历后就能找出只出现一次的数据还是有一定的难度的。我在第一次看这个题目的时候也没想到什么好的办法。最后看了书上的提示才想到这个解决方案。
首先我们考虑只有一个数字出现的情况,这时候我们该怎么做呢?也就是其他的数字都是成对的出现,而唯独只有一个数字是单独出现。注意这句话非常重要。成对出现的数字有什么特点吗?这里有点难想到,就是这个切入点很难想到,那就是异或运算。相同的数字经过异或运算后,就变为了0。然后如果一个数字和0异或运算就是它本身。说到这儿,应该就明白了吧!我们将数组里的每个数字进行异或运算,如果只有一个数字最后出现过一次,那么经过异或完以后,最后剩下的那个不为0的数字就是我们要找的那个数字。因为成对出现的都消失了,剩下的就是孤苦无依的那个单的,呵呵!
看完了只有一个数字出现一次的情况,我们再来看看有两个数字出现的情况。想想成对的经过异或运算都化为了0.那么剩下了两个数据怎么样了呢?一定是不为0,那么我们可以寻找这两个数字的不同,哪里不同?异或运算不为0就是不同啊,所以我们将数字同过异或完最后结果的倒数第一位不是0的二进制位来将数组分为两组,这样的话就把我们需要找的两个数字分到了两个数组里,并且在这个两个子数组里,除了一个数字出现一次外,其他的都是成对的出现,这下好了,问题转化为我们讨论的第一种情况,那么困难也就迎刃而解了!
具体实现代码:
#include <iostream> using namespace std; int arr[8]={2,4,3,6,3,2,5,5}; //判断二进制位是否是1; bool IsBit1(int num,unsigned int indexBit) { num=num>>indexBit; return num&1; } //找到数组进行异或操作后第一个是1的二进制位。 unsigned int FindFirstBitIs1(int num) { int indexBit=0; while((num & 1)==0 && (indexBit<8*sizeof(int))) { num=num>>1; ++indexBit; } return indexBit; } //找到出现一次的的数子 void FindNumAppearOnce(int *arr,int length,int *num1,int *num2) { if(arr==NULL || length<2) return ; int resultExclusiveOR=0; for(int i=0;i<length;++i) resultExclusiveOR^=arr[i]; unsigned int indexof1=FindFirstBitIs1(resultExclusiveOR); *num1=*num2=0; for(int j=0;j<length;++j) { if(IsBit1(arr[j],indexof1)) *num1^=arr[j]; else *num2^=arr[j]; } } int main() { int num1,num2; FindNumAppearOnce(arr,8,&num1,&num2); cout<<"只出现一次的两个数为:"<<num1<<" "<<num2<<endl; system("pause"); return 0; }
运行结果: