本内容转自:https://www.cnblogs.com/Kobe10/p/6306183.html#undefined,参考:https://blog.csdn.net/zzran/article/details/8108787 为了方便加深印象,本身又照着从新写了一遍。人家这篇博客写的有思想、有深度,想着要是哪一天我也能独自总结出这样的精髓,必定要买颗糖奖励一下,哈哈.......皮一下?html
第一次接触到这种比较伤脑细胞的题,这是一个关于位运算比较简单的入门题,原题是这样描述的:在一个整型数组中有一个元素只出现一次,其它元素都出现两次。求出只出现一次的元素。(要求:线性时间复杂度,不能使用额外空间。聪明的你能搞定吗?)。很明确的说,我不聪明,也搞不定,按照我那榆木脑壳的思惟,必定会先给它们排排序,再找出那只出现一次的的元素。程序代码以下:ios
1 //很明显这里数组元素的个数必定是奇数个 2 #include<iostream> 3 #include<algorithm> 4 using namespace std; 5 int main(){ 6 int n; 7 cin>>n; 8 int a[n]; 9 for(int i=0;i<n;i++) 10 cin>>a[i]; 11 sort(a,a+n);//排序 12 for(int i=0;i<n;i+=2){ 13 if(a[i]==a[i+1]) 14 continue; 15 else{ 16 cout<<a[i]<<endl; 17 break; 18 } 19 } 20 return 0; 21 }
上面程序数据测试没有问题,但网站没法AC,说得好听点:就是没法达到题目所要求的线性时间复杂度,说得难听点:这代码写了跟没写同样。接下来请看大神的思惟:数组
这个题目的突破口在哪里?题目为何要强调有一个数字出现一次,其余的出现两次?咱们想到了异或运算的性质:(任何一个数字异或它本身都等于0,任何一个数字和0异或都等于它自己)也就是说,若是咱们从头至尾依次异或数组中的每个数字,那么最终的结果恰好是那个只出现一次的数字,由于那些出现两次的数字所有在异或中抵消掉了。函数
举个栗子:2 3 4 2 3测试
全部数字依次异或运算:2 ^ 3 ^ 4 ^ 2 ^ 3 = (2 ^ 2) ^ (3 ^ 3) ^ 4= 0 ^ 0 ^ 4 = 4 (知足分配律)网站
根据以上思惟,代码以下:spa
1 #include<iostream> 2 using namespace std; 3 int main(){ 4 int n,num; 5 cin>>n; 6 int a[n]; 7 for(int i=0;i<n;i++) 8 cin>>a[i]; 9 for(int i=0;i<n;i++) 10 num^=a[i]; 11 cout<<num<<endl; 12 return 0; 13 }
当你看到这的时候,你觉得就完了???兄弟请不要急忙离开,请往下看,还有一个更头疼,更伤脑筋的问题:一个整型数组里除了两个数字以外,其余的数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
.net
这是上一问题的升级版,若是仍是用排序的方法,很遗憾,那你就能够淘汰出局了!有了上面简单问题的解决方案以后,咱们回到原始的问题。若是可以把原数组分为两个子数组。在每一个子数组中,包含一个只出现一次的数字,而其余数字都出现两次。若是可以这样拆分原数组,按照前面异或的办法就是分别求出这两个只出现一次的数字了。咱们仍是从头至尾依次异或数组中的每个数字,那么最终获得的结果就是两个只出现一次的数字的异或结果。由于其余数字都出现了两次,在异或中所有抵消掉了。因为这两个数字确定不同,那么这个异或结果确定不为0,也就是说在这个结果数字的二进制表示中至少就有一位为1。咱们在结果数字中找到第一个为1的位的位置,记为第N位。如今咱们以第N位是否是1为标准把原数组中的数字分红两个子数组,第一个子数组中每一个数字的第N位都为1,而第二个子数组的每一个数字的第N位都为0(不知道这段黑体字你有没有看懂,看不懂的话本身在草稿纸上举个例子就很容易明白了)。如今咱们已经把原数组分红了两个子数组,每一个子数组都包含一个只出现一次的数字,而其余数字都出现了两次。所以到此为止,全部的问题咱们都已经解决。3d
基于上述思路,咱们不难写出以下代码:code
void FindNumsAppearOnce(int data[], int length, int &num1, int &num2){ if (length < 2) return; int resultExclusiveOR = 0; for (int i = 0; i < length; ++ i) resultExclusiveOR ^= data[i];//获得数组中只出现一次的数字的异或结果 unsigned int indexOf1 = FindFirstBitIs1(resultExclusiveOR);//索引异或结果的二进制位数中第一位为1的位数(从右向左第一位),并将位数存放在indexOf1中 num1 = num2 = 0; for (int j = 0; j < length; ++ j){//分组 if(IsBit1(data[j], indexOf1)) num1 ^= data[j]; else num2 ^= data[j]; } } unsigned int FindFirstBitIs1(int num){//找到第一位为1的位数 int indexBit = 0; while (((num & 1) == 0) && (indexBit < 32)){//这里<32,是为了防止越界,由于int数据类型最大只有2^32,只有32位 num = num >> 1; ++ indexBit; } return indexBit; } bool IsBit1(int num, unsigned int indexBit){//判断是否为1 num = num >> indexBit; return (num & 1); }
完整代码以下:
1 #include<iostream> 2 using namespace std; 3 int find_1(int num){ 4 int m=0;//存放第一位为1的位数 5 while(((m&1)==0)&&m<32){ 6 num=num>>1;//若是第一位不是1,右移继续判断,直到为1中止,并返回m 7 m++; 8 } 9 return m; 10 } 11 bool is_1(int x,int m){ 12 x=x>>m; 13 return (x&1); 14 } 15 void f(int a[],int n,int &num1,int &num2){ 16 if(n<=2) 17 return ; 18 int num1_OR_num2=0;//存放只出现一次两个数的异或结果 19 for(int i=0;i<n;i++) 20 num1_OR_num2^=a[i]; 21 int locateIS1=find_1(num1_OR_num2);//获得异或结果中第一位为1的位数 22 num1=num2=0; 23 for(int i=0;i<n;i++){ 24 if(is_1(a[i],locateIS1)){ 25 num1^=a[i]; 26 } 27 else{ 28 num2^=a[i]; 29 } 30 } 31 } 32 int main(){ 33 int n,num1,num2; 34 cin>>n; 35 int a[n]; 36 for(int i=0;i<n;i++) 37 cin>>a[i]; 38 f(a,n,num1,num2); 39 cout<<num1<<" "<<num2<<endl; 40 return 0; 41 }
运行结果以下:
当你看到这时,请君稍安勿躁,请继续再往下看:
题目:一个数组中有三个数字a、b、c只出现一次,其余数字都出现了两次。请找出三个只出现一次的数字。(与最前面的一题不一样,前面是2个不一样,如今是3个)
分析:在这道题中,若是咱们可以找出一个只出现一次的数字,剩下两个只出现一次的数字就很容易找出来了。若是咱们把数组中全部数字都异或起来,那最终的结果(记为x)就是a、b、c三个数字的异或结果(x=a^b^c)。其余出现了两次的数字在异或运算中相互抵消了。咱们能够证实异或的结果x不多是a、b、c三个互不相同的数字中的任何一个。咱们用反证法证实。假设x等于a、b、c中的某一个。好比x等于a,也就是a=a^b^c。所以b^c等于0,即b等于c。这与a、b、c是三个互不相同的三个数相矛盾。因为x与a、b、c都各不相同,所以x^a、x^b、x^c都不等于0。咱们定义一个函数f(n),它的结果是保留数字n的二进制表示中的最后一位1,而把其余全部位都变成0。好比十进制6表示成二进制是0110,所以f(6)的结果为2(二进制为0010)。f(x^a)、f(x^b)、f(x^c)的结果均不等于0。接着咱们考虑f(x^a)^f(x^b)^f(x^c)的结果。因为对于非0的n,f(n)的结果的二进制表示中只有一个数位是1,所以f(x^a)^f(x^b)^f(x^c)的结果确定不为0。这是由于对于任意三个非零的数i、j、k,f(i)^f(j)的结果要么为0,要么结果的二进制结果中有两个1。不论是那种状况,f(i)^f(j)都不可能等于f(k),由于f(k)不等于0,而且结果的二进制中只有一位是1。因而f(x^a)^f(x^b)^f(x^c)的结果的二进制中至少有一位是1。假设最后一位是1的位是第m位。那么x^a、x^b、x^c的结果中,有一个或者三个数字的第m位是1。
接下来咱们证实x^a、x^b、x^c的三个结果第m位不可能都是1。仍是用反证法证实。若是x^a、x^b、x^c的第m位都是1,那么a、b、c三个数字的第m位和x的第m位都相反,所以a、b、c三个数字的第m位相同。若是a、b、c三个数字的第m位都是0,x=a^b^c结果的第m位是0。因为x和a两个数字的第m位都是0,x^a结果的第m位应该是0。同理能够证实x^b、x^c第m位都是0。这与咱们的假设矛盾。若是a、b、c三个数字的第m位都是1,x=a^b^c结果的第m位是1。因为x和a两个数字的第m位都是1,x^a结果的第m位应该是0。同理能够证实x^b、x^c第m位都是0。这仍是与咱们的假设矛盾。所以x^a、x^b、x^c三个数字中,只有一个数字的第m位是1。因而咱们找到了可以区分a、b、c三个数字的标准。这三个数字中,只有一个数字知足这个标准,而另外两个数字不知足。一旦这个知足标准数字找出来以后,另外两个数字也就能够找出来了。
代码以下:
1 #include<iostream> 2 #include<stdio.h> 3 using namespace std; 4 int get_first_bit(int num) 5 { 6 return num&~(num-1); 7 } 8 void get_two_unique_num(int *a,int n,int *num1,int *num2) 9 { 10 int result_code=0; 11 for(int i=0;i<n;i++) 12 result_code^=a[i]; 13 int diff=get_first_bit(result_code); 14 *num1=0; 15 *num2=0; 16 for(int i=0;i<n;i++) 17 { 18 if(a[i]&diff) 19 { 20 (*num1)^=a[i]; 21 } 22 else 23 { 24 (*num2)^=a[i]; 25 } 26 } 27 } 28 void get_three_unique_num(int *a,int n,int *num1,int *num2,int *num3) 29 { 30 int result_code=0; 31 for(int i=0;i<n;i++) 32 result_code^=a[i]; 33 int flag=0; 34 for(int i=0;i<n;i++) 35 flag^=get_first_bit(result_code^a[i]); 36 flag=get_first_bit(flag); 37 *num1=0; 38 for(int i=0;i<n;i++) 39 { 40 if(get_first_bit(result_code^a[i])==flag) 41 { 42 (*num1)^=a[i]; 43 } 44 } 45 for(int i=0;i<n;i++) 46 { 47 if(a[i]==(*num1)) 48 { 49 int temp=a[i]; 50 a[i]=a[n-1]; 51 a[n-1]=temp; 52 break; 53 } 54 } 55 get_two_unique_num(a,n-1,num2,num3); 56 } 57 int main() 58 { 59 int n; 60 cin>>n; 61 int a[n]; 62 for(int i=0;i<n;i++) 63 cin>>a[i]; 64 int num1,num2,num3; 65 get_three_unique_num(a,n,&num1,&num2,&num3); 66 cout<<num1<<" "<<num2<<" "<<num3<<endl; 67 return 0; 68 }
运行结果以下: