关于位运算(转)

本内容转自:https://www.cnblogs.com/Kobe10/p/6306183.html#undefined,参考:https://blog.csdn.net/zzran/article/details/8108787 为了方便加深印象,自己又照着重新写了一遍。人家这篇博客写的有思想、有深度,想着要是哪一天我也能独自总结出这样的精髓,一定要买颗糖奖励一下,哈哈.......皮一下?

  第一次接触到这种比较伤脑细胞的题,这是一个关于位运算比较简单的入门题,原题是这样描述的:在一个整型数组中有一个元素只出现一次,其它元素都出现两次。求出只出现一次的元素。(要求:线性时间复杂度,不能使用额外空间。聪明的你能搞定吗?)。很明确的说,我不聪明,也搞不定,按照我那榆木脑袋的思维,一定会先给它们排排序,再找出那只出现一次的的元素。程序代码如下:

 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  (满足分配律)

根据以上思维,代码如下:

 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)

  这是上一问题的升级版,如果还是用排序的方法,很遗憾,那你就可以淘汰出局了!有了上面简单问题的解决方案之后,我们回到原始的问题。如果能够把原数组分为两个子数组。在每个子数组中,包含一个只出现一次的数字,而其他数字都出现两次。如果能够这样拆分原数组,按照前面异或的办法就是分别求出这两个只出现一次的数字了。我们还是从头到尾依次异或数组中的每一个数字,那么最终得到的结果就是两个只出现一次的数字的异或结果。因为其他数字都出现了两次,在异或中全部抵消掉了。由于这两个数字肯定不一样,那么这个异或结果肯定不为0,也就是说在这个结果数字的二进制表示中至少就有一位为1。我们在结果数字中找到第一个为1的位的位置,记为第N位。现在我们以第N位是不是1为标准把原数组中的数字分成两个子数组,第一个子数组中每个数字的第N位都为1,而第二个子数组的每个数字的第N位都为0(不知道这段黑体字你有没有看懂,看不懂的话自己在草稿纸上举个例子就很容易明白了)。现在我们已经把原数组分成了两个子数组,每个子数组都包含一个只出现一次的数字,而其他数字都出现了两次。因此到此为止,所有的问题我们都已经解决。

基于上述思路,我们不难写出如下代码:

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 }  

运行结果如下:

原文地址:https://www.cnblogs.com/geziyu/p/9687242.html