一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
数字都出现两次,则异或肯定为0。
考虑下这个题目的简化版——数组中除一个数字只出现1次外,其它数字都成对出现,要求尽快找出这个数字。根据异或运算的特点,直接异或一次就可以找出这个数字。
现在数组中有两个数字只出现1次,直接异或一次只能得到这两个数字的异或结果,但光从这个结果肯定无法得到这个两个数字。因此我们来分析下简化版中“异或”解法的关键点,这个关键点也相当明显——数组只能有一个数字出现1次。
设题目中这两个只出现1次的数字分别为A和B,如果能将A,B分开到二个数组中,那显然符合“异或”解法的关键点了。因此这个题目的关键点就是将A,B分开到二个数组中。由于A,B肯定是不相等的,因此在二进制上必定有一位是不同的。根据这一位是0还是1可以将A,B分开到A组和B组。而这个数组中其它数字要么就属于A组,要么就属于B组。再对A组和B组分别执行“异或”解法就可以得到A,B了。而要判断A,B在哪一位上不相同,只要根据A异或B的结果就可以知道了,这个结果在二进制上为1的位都说明A,B在这一位上是不相同的。
比如int a[] = {1, 1, 3, 5, 2, 2}
整个数组异或的结果为3^5即 0x0011 ^ 0x0101 = 0x0110
对0x0110,第1位(由低向高,从0开始)就是1。因此整个数组根据第1位是0还是1分成两组。
a[0] =1 0x0001 第一组
a[1] =1 0x0001 第一组
a[2] =3 0x0011 第二组
a[3] =5 0x0101 第一组
a[4] =2 0x0010 第二组
a[5] =2 0x0010 第二组
第一组有{1, 1, 5},第二组有{3, 2, 3},明显对这二组分别执行“异或”解法就可以得到5和3了。
1 class Solution {
2 public:
3 void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) {
4 int temp=0;
5 int n=data.size();
6 if(n<2) return;
7 int i,j;
8 for(i=0;i<n;i++)
9 temp^=data[i];
10 for(j=0;j<sizeof(int)*8;j++){
11 if((temp>>j)&1==1)
12 break;
13 }
14 for(i=0;i<n;i++){
15 if((data[i]>>j)&1==1){
16 *num1^=data[i];
17 }
18 else
19 *num2^=data[i];
20 }
21 }
22 };
扩展:数组A中,除了某一个数字x之外,其他数字都出现了三次,而x出现了一次。请给出最快的方法找到x。
我们换一个角度来看,如果数组中没有x,那么数组中所有的数字都出现了3次,在二进制上,每位上1的个数肯定也能被3整除。如{1, 5, 1, 5, 1, 5}从二进制上看有:
1:0001
5:0101
1:0001
5:0101
1:0001
5:0101
二进制第0位上有6个1,第2位上有3个1.第1位和第3位上都是0个1,每一位上的统计结果都可以被3整除。而再对该数组添加任何一个数,如果这个数在二进制的某位上为1都将导致该位上1的个数不能被3整除。因此通过统计二进制上每位1的个数就可以推断出x在该位置上是0还是1了,这样就能计算出x了。
推广一下,所有其他数字出现N(N>=2)次,而一个数字出现1次都可以用这种解法来推导出这个出现1次的数字。
1 // 【白话经典算法系列之十七】<span style="color:#000000;font: 14px/23px arial; text-transform: none; text-indent: 0px; letter-spacing: normal; word-spacing: 0px; float: none; display: inline !important; white-space: normal; font-size-adjust: none; font-stretch: normal; -webkit-text-stroke-width: 0px;">数组中只出现一次的数</span>
2 // by MoreWindows( http://blog.csdn.net/MoreWindows )
3 // 欢迎关注http://weibo.com/morewindows
4 #include <stdio.h>
5 #include <string.h>
6 int FindNumber(int a[], int n)
7 {
8 int bits[32];
9 int i, j;
10 // 累加数组中所有数字的二进制位
11 memset(bits, 0, 32 * sizeof(int));
12 for (i = 0; i < n; i++)
13 for (j = 0; j < 32; j++)
14 bits[j] += ((a[i] >> j) & 1);
15 // 如果某位上的结果不能被整除,则肯定目标数字在这一位上为
16 int result = 0;
17 for (j = 0; j < 32; j++)
18 if (bits[j] % 3 != 0)
19 result += (1 << j);
20 return result;
21 }
22 int main()
23 {
24 printf(" 【白话经典算法系列之十七】数组中只出现一次的数\n");
25 printf(" -- by MoreWindows( http://blog.csdn.net/MoreWindows ) --\n");
26 printf(" -- javascript:void(0) -- \n\n");
27
28 const int MAXN = 10;
29 int a[MAXN] = {2, 3, 1, 2, 3, 4, 1, 2, 3, 1};
30 printf("%d\n", FindNumber(a, MAXN));
31 return 0;
32 }
运行结果如下图所示: