Single Number (Bit Manipulation)

題目描述

給你一個數(shù)組,除了x,數(shù)組中每個元素出現(xiàn)2次,讓你求解出這個x。
[leetcode136]https://leetcode.com/problems/single-number/

思路

  1. 使用hashMap是非常容易理解并求解的不再累述。
  2. 接下來就是我們的位操作,這一類題的一個通用的解法。

算法步驟

對這個數(shù)組的每一位進行異或,最后的結(jié)果就是那個單數(shù)來的數(shù)。

算法原理

異或操作滿足:ab=ba,aa=0,0a=a
所以一趟異或操作之后,那些出現(xiàn)偶數(shù)次的數(shù)對結(jié)果的貢獻就沒有影響,這些所有的出現(xiàn)偶數(shù)次的數(shù)異或之后為0,而0與那個單獨出現(xiàn)的數(shù)x異或之后就為x。

算法代碼

public class Solution {
    public int singleNumber(int[] nums) {
        int res=0;
        for(int i:nums)
            res^=i;
        return res;
    }
}

算法原理很簡單,實現(xiàn)也很簡單。但這里需要注意的是,那些出現(xiàn)多次的必須是出現(xiàn)偶數(shù)次,不然這個算法就失效了,那么對于出現(xiàn)奇數(shù)次的怎么辦呢?接下來拓展詳細描述這一類題目的解法。

拓展一

[leetcode]https://leetcode.com/problems/single-number-ii/
這次數(shù)組中除了x外,每個元素出現(xiàn)3次,需要你找出x。注意到次數(shù)3為奇數(shù)了,上述方法失效。

思路

  1. 當然了也可以使用一個hash,類似于上題的hash解法,不用改動代碼即可實現(xiàn)。當然了,這種解法很low。不在詳述。
public class Solution {
    public int singleNumber(int[] nums) {
        Map<Integer,Integer> hashMap=new HashMap<Integer,Integer>();
        for(int i=0;i<nums.length;i++){
            if(!hashMap.containsKey(nums[i]))
                hashMap.put(nums[i],1);
            else
                hashMap.put(nums[i],2);
        }
        for(int i=0;i<nums.length;i++){
            if(hashMap.get(nums[i])==1)
                return nums[i];
        }
        return -1;
    }
}
  1. 使用位操作
    接下來介紹三種使用位操作的方法,其實他們的本質(zhì)是一樣的。

位操作方法一

算法步驟

  1. 因為int是32位,所以我們考慮使用一個長度為32的數(shù)組,初始化為0。
  2. 記res為最后的結(jié)果,由于res也為32位的int,我們只需要確定,res的每一位為1還是0即可,所以這里使用了一個外層32次的循環(huán),內(nèi)層函數(shù)每執(zhí)行一次,確定了res的一個比特位。
  3. 假設現(xiàn)在需要確定res的第i比特位,內(nèi)層函數(shù)找出原數(shù)組中在第i比特位上為1的數(shù),統(tǒng)計這些數(shù)的個數(shù),然后對3取模,結(jié)果要么是0,要么是1(因為本題中那個特殊的數(shù)只出現(xiàn)一次)這個結(jié)果就是我們的res的第i比特位的值。
  4. 32趟走完時候我們可以確定res的每一個比特位是0還是是1,也就可以確定res的值了。

算法原理

因為原數(shù)組中除了數(shù)res外,每個數(shù)都出現(xiàn)三次,所以我考察這些數(shù)的對應比特位。那些出現(xiàn)三次的數(shù),對應為1的比特位相加后為3的倍數(shù),只有這個只出現(xiàn)1次的數(shù)的那些為1的bit位,這些位數(shù)上count為3n+1,所以我們相加取模時候就可以確定哪些比特位為1。
比如[1,2,2,2,3,3,3]

 num      a  b
  1    ->  0   1
  2    ->  1   0
  3    ->  1   1

2和3都出現(xiàn)了三次,所以我們把對應比特位1的個數(shù)相加的話a比特位一共6個1,b比特位一共4個1,對3取模后a比特為0,b比特位1。我們就知道結(jié)果為0。

算法代碼

public class Solution {
    public int singleNumber(int[] nums) {
        int[] count=new int[32];
        int res=0;
        for(int i=0;i<32;i++){
            for(int j=0;j<nums.length;j++){
                if(((nums[j]>>i)&1)==1)
                    count[i]++;
            }
            res|=((count[i]%3)<<i);
        }
        return res;
    }
}
//當然,一般化,假如出現(xiàn)多次的數(shù)出現(xiàn)次數(shù)為k,只需要模k即可。
//另外,如果那個特殊的數(shù)不是出現(xiàn)1次,
//修改res|=((count[i]%3)<<i)即可,
//例如假如出現(xiàn)2次,
//只需要修改為res|=((count[i]%3==0?0:1)<<i)

位操作方法二

算法步驟

  1. 使用三個變量,ones twos xthree,如果二進制表示中,某個數(shù)位上"1"出現(xiàn)一次,那么ones在這個數(shù)位上就是"1",同理twos表示出現(xiàn)兩次。
  2. 看看ones twos 的變化規(guī)則。新進來一個數(shù)nums[i],考察ones&nums[i],如果ones在這個比特位上為1,表面之前這個比特位"1"出現(xiàn)過一次,現(xiàn)在nums[i]在這個比特位上又為1,那么自然就是出現(xiàn)了兩次"1",所以twos在這個比特位上就應該為1。ones的變化規(guī)則很簡單,異或就行了。需要注意的是如果某個比特位在ones和twos中都為1,那么就代表出現(xiàn)了三次,需要把這個比特位清零。
  3. 遍歷結(jié)束后,ones就是出現(xiàn)一次的數(shù),twos就是出現(xiàn)兩次的數(shù)。

算法原理

從比特位來考慮,出現(xiàn)三次的數(shù)在遍歷結(jié)束之后對ones和twos都沒有影響,因為他們最后會在他們比特位表示為"1"的位置上出現(xiàn)三次,他們的影響被代碼

xthree = ~(ones & twos);
            ones&=xthree;
            twos&=xthree;

抹去,最后ones的各個比特位就是只出現(xiàn)一次的那個數(shù)的比特位,twos的各個比特位就是只出現(xiàn)兩次的那個數(shù)的比特位。

算法代碼

public class Solution {
    public int singleNumber(int[] nums) {
        //2,2,3,2
        int ones=0;//二進制表示中“1”出現(xiàn)一次的數(shù)位
        int twos=0;//二進制表示中“1”出現(xiàn)兩次的數(shù)位
        int xthree=0;
        for(int i=0;i<nums.length;i++){
            twos|=(ones&nums[i]);//出現(xiàn)兩次,當然是“之前”位置上首先就是“1”(ones),然后輸入的數(shù)樹在這個位置上又是“1”
            ones^=nums[i];//出現(xiàn)一次,當然是異或就行了,這樣出現(xiàn)一次的變?yōu)?,出現(xiàn)0次的變?yōu)?
            xthree = ~(ones & twos);//一個數(shù)位在ones和twos同時為1,表示當前數(shù)位出現(xiàn)了3次,應該把這個數(shù)位變?yōu)?
            ones&=xthree;
            twos&=xthree;
        }
        return ones;
    }
}
//拓展,two最終記錄的是出先兩次的
//http://www.cnblogs.com/daijinqiao/p/3352893.html
//[1,2,2,2,3,3,4,4,4,5,5,5]
//[1,2,2,3,3,3,4,4,4,5,5,5]

位操作方法三

這個方法其實和方法二一樣,只不過搞得有點像數(shù)電設計中的狀態(tài)轉(zhuǎn)移。

算法步驟&原理

a表示32位的int,a的每一位怎么確定,在遍歷原數(shù)組過程中,考察原數(shù)組中數(shù)的比特位表示,如果該位上"1" 出現(xiàn)次數(shù)為2,那么該位就為1,否則為0
同理
a表示32位的int,b的每一位怎么確定,在遍歷原數(shù)組過程中,考察原數(shù)組中數(shù)的比特位表示,如果該位上"1" 出現(xiàn)次數(shù)為1,那么該位就為1,否則為0
那么我們很容易得到下面的狀態(tài)轉(zhuǎn)移表
(ai為a的i比特位,bi為b的i比特位,ci為新遍歷到的數(shù)的i比特位)

 ai    bi    ci     ai’   bi'
 0     0     0      0     0
 0     1     0      0     1
 1     0     0      1     0
 0     0     1      0     1
 0     1     1      1     0
 1     0     1      0     0

我們需要找到出現(xiàn)次數(shù)為1的比特位,也就是bi'為1
順便找到出現(xiàn)次數(shù)為2的比特位,也就是ai'為1
所以ai'=aibici|~aibici
bi'=aibici|aibici
我們就可以確定出現(xiàn)次數(shù)為1的比特位,也就可以確定出現(xiàn)次數(shù)為1的數(shù)。
這里的代碼是同時找了次數(shù)為1的和為2的(這種理解是特殊的數(shù)出現(xiàn)一次或兩次,二者選其一)

算法代碼

public class Solution {
    public int singleNumber(int[] nums) {
        int a=0;
        int b=0;
        for(int c:nums){
            int tmpa=a&(~b)&(~c)|((~a)&b&c);
            b=(~a&~b&c)|(~a&b&~c);
            a=tmpa;
        }
        return a|b;
    }
}

當然,如果了解轉(zhuǎn)臺轉(zhuǎn)移化簡的話,可以簡化為

 for(int c:nums){
            int tmpa=a&(~c)|(b&c);
            b=(~a&~b&c)|(b&~c);
            a=tmpa;
        }

拓展二

[leetcode260]https://leetcode.com/problems/single-number-iii/
這次是數(shù)組中有兩個不同的數(shù)都只出現(xiàn)過一次,其他數(shù)都出現(xiàn)兩次,讓找出這兩個數(shù)。

算法描述

  1. 對這些數(shù)進行異或操作得到ones
  2. 移位與操作得到once的比特位表示的第一個非“0”位置,并把一個數(shù)賦值為僅該位置為1,其他位置為0
  3. 用這個數(shù)去和數(shù)組中的數(shù)做與運算,根據(jù)與運算結(jié)果是否為0可以把原數(shù)組中的數(shù)分為兩個陣營,分別對兩個陣營進行異或操作,最終得到的兩個結(jié)果就是所求的兩個數(shù)。

算法原理

有兩個數(shù)a,b只出現(xiàn)一次,其他兩次,那么所有數(shù)進行異或之后的結(jié)果ones,ones比特位表示為1的位置肯定就是a和b在這個位置為一個1一個0,那么就可以使用這個位置把a和b分到不同的陣營中,兩個陣營都是包含a/b和一堆成對出現(xiàn)的數(shù),對他們進行異或操作即可求出a/b。

算法代碼

public class Solution {
  public int[] singleNumber(int[] nums) {
      int one=0;
      for(int i:nums)
          one^=i;
      int tmp=1;
      while((one&1)!=1){
          one>>=1;
          tmp<<=1;
      }
      int res1=0;
      int res2=0;
      for(int i:nums){
          if((i&tmp)==0){
              res1^=i;
          }
          else
              res2^=i;
      }
      int[] res=new int[2];
      res[0]=res1;
      res[1]=res2;
      return res;
  }
}

當然了,繼續(xù)拓展的話,如果有兩個數(shù)出現(xiàn)1次,其他書出現(xiàn)三次,那么就是拓展一中的方法先找到為出現(xiàn)次數(shù)為1的兩個數(shù)共同影響產(chǎn)生的結(jié)果ones,然后找到一個為“1”的比特位,然后分陣營,就變?yōu)閮蓚€拓展一類型的問題。
如果一個一次一個兩次其他三次,那么一次的就是ones,兩次的就是twos。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容