问题:一个整型数组中除了两个数字之外,其他的数字都出现了两次,写出程序找出这两个只出现了一次的数字,时间复杂度要求O(n),空间复杂度要求O(1).
一看到这个题想到了很多“可能”的解法,很飘散的思绪,比如什么哈希啊,什么加A再减去A,剩下的就是单独的了啊,都是写没啥用的解法。
后面看到了书上的解法,觉得很巧妙,于是贴了上来,顺便标明了自己当时不是很清楚的理解。
书上说这道题很难,我觉得算法这种东西,本质上并没有难一说,而是主张一个“巧妙”,九拐十八弯。
从易入难,举一反三是算法学习中必不可少的一种经历。
闲话少说,言归正传,首先这个题目是要求我们找出数组中的两个分别出现了一次的数字,而且时间复杂度要求O(n),如果说我们想靠找到数组中的一个A再在后面找到另一个A,这样的话,时间复杂度完全不符合要求,相应的时间复杂度为O(n^2),太。。。。慢了。。
那么怎样才能解决这个问题呢?
根据书上的提示,我们先来考虑一个比较 简单的场景,即数组中只有一个单独的不成对的数,我们可以通过连续对数组中的元素求异或(“^”操作),因为A和A的异或结果是为零的,根据这个思想,我们可以把异或的结果保存到一个result变量里,到最后这个变量的结果就是我们所求的那个单独的数。(当时困扰楼主了一会儿,让lz感觉到莫名其妙的是书上的”依次异或“这句话,楼主心想,依次异或,难不成还是依次便利数组中的元素,求异或?这样的复杂度还是O(n^2),太诡异了,现在才发现自己当时的想法是错误的,曲解了作者的想法,希望有这么理解的同学不要再这么想了~)
接下来考虑数组中拥有两个独立数据的情况,对这个数组中的元素同样进行上述的异或操作,得到的最终结果是两个不同的数的异或结果,由于两个数不相同,那么这个结果中肯定不为零,也就是存在1的位数,我们可以根据此结果把这个数组分成两个子数组,从低到高位,第一个1出现的位数为1的数和这位为0的数为两个数组,那么肯定的是,这两个单独的数分别被分到了两个子数组里,那么就好办了,对这两个子数组的元素分别进行异或操作,得到的就是这两个单独的数。
根据此思路完成的代码如下:
2 using namespace std;
3
4 void find_two_aloneC( int a[], int length)
5 {
6 int i,j= 1,Xor= 0,Xor_0= 0,Xor_1= 0;
7
8 for(i = 0;i<length;i++)
9 Xor = Xor^a[i];
10 while(((Xor & 1) == 0))
11 {
12 Xor = (Xor >> 1);
13 j = j << 1;
14 }
15 for(i = 0;i<length;i++)
16 {
17 if((a[i] & j) == 0)
18 {
19 Xor_0 = Xor_0^a[i];
20 }
21 else if((a[i] & j )!= 0)
22 {
23 Xor_1 = Xor_1^a[i];
24 }
25 }
26 cout << Xor_0 << endl;
27 cout << Xor_1 << endl;
28 }
29 int main()
30 {
31 int a[] = { 2, 3, 4, 5, 3, 3, 3, 5, 2, 5};
32 find_two_aloneC(a, 10);
33 system( " pause ");
34
35 return 0;
36 }