關於位運算(轉)

本內容轉自: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 }  

運行結果以下:

相關文章
相關標籤/搜索