Leetcode 137 Single Number II 僅出現一次的數字

原題地址

https://leetcode.com/problems/single-number-ii/

題目描述

Given an array of integers, every element appears three times except for one. Find that single one.
給出一個整數數組,除了某個元素外所有元素都出現三次。找出僅出現一次的數字。

Note:
注意:

Your algorithm should have a linear runtime complexity. Could you implement it without using extra memory?
你的算法需要在線性時間複雜度內運行。你可以在常數空間複雜度內實現嗎?要求:時間複雜度O(n)空間複雜度O(1)

Tags Bit Manipulation

解題思路

Single Number系列的第二題,第一題請看 Leetcode 136 Single Number 僅出現一次的數字

實際上136題是137題都是一個特例,更抽象的題目是,有一個數組,其中有一個元素出現了x次,其餘所有元素都出現了y次(x < y),找出這個出現了x次的元素。關於這個問題,我們在另一篇文章裏進行討論(請看Leetcode Single Number 擴展)。這裏我們只討論137這個題目。

由於除去目標元素target之外,所有元素都出現3次,假設出現3次的元素有n個,這樣的話假如我們統計所有元素的某一位(比如最後一位),其一共有3n+1個二進制位。因爲對與同一個元素來說,其所有的二進制位一定是相同的,所以對這些元素的某一位來說一定是以3個1或3個0爲單位出現的,即3n+1個二進制位中一定是3x個1和3y個0,其中x+y=n,再外加一個target對應的二進制位(1或0都有可能)。綜上所述,我們可以統計所有數字每一位上1的個數,對3取模,如果爲1就說明target對應位爲1,否則爲0

下面問題就是如何統計每一位上1的個數,一個比較好的方法就是採用位運算來處理,當個數滿3時就清零(當然這是參考的網上大神們的思路)。

我們用三個整數one,two, three的二進制位來分別表示32位整數某一位上1出現次數是否爲1次、2次、3次,舉例,假如:

one = 1 --- 0x00000001 --- 00000000 00000000 00000000 00000001
則表示當前統計情況下最低位出現1的次數爲1次
two = 3 --- 0x00000003 --- 00000000 00000000 00000000 00000011
則表示當前統計情況下最低位出現1的次數爲2次,倒數第二位出現1的次數爲2次
three = 4 --- 0x00000004 --- 00000000 00000000 00000000 00000100
則表示當前統計情況下最倒數第三位出現1的次數爲3次

大神們說one two three可以稱之爲掩碼。

有了如上邏輯後,我們可以遍歷所有的數字,對於每個數字,操作其所有的二進制位,來更新one two three三個數字。當我們遍歷完所有數字時,由於除去target只出現一次外,其餘元素都是以3爲單位出現的,所以可以知道one中存儲的二進制位代表的數字就是target。

對於實際代碼中,one two three三者的更新,有以下兩個版本,版本一是我自己想出來的,更通用,而且更接近於上面提出的更一般的問題的解決思路(代碼一);版本二是網上大神們的代碼,只能說更巧妙一些,但是實際上都一樣(代碼二)。詳細請看代碼部分中的註釋。

代碼

代碼一

int singleNumber(int* nums, int numsSize) {
    int one = 0, two = 0, three = 0, tmp;
    while (numsSize) {
        // 獲取當前要統計的數字
        tmp = *(nums + --numsSize); 
        // 之前已經出現2次1的位且當前數字中又爲1,則更新爲出現3次
        three = two & tmp; 
        // 清空上一步操作中出現3次1的位
        two &= ~three;  // 在two中清空
        tmp &= ~three;  // 當前數字也要清空,以防重複統計
        // 之前已經出現1次1的位且當前數字中又爲1,則更新爲出現2次
        two |= one & tmp;
        // 之前沒有出現且當前數字中爲1,更新爲出現1次
        // 或者之前出現次數爲1次,且當前數字中沒有出現,仍舊爲1次
        one ^= tmp;
    }
    return one;
}

其中需要注意的主要有兩點,第一是使用tmp更新three之後,需要對two和tmp進行滿3次的位的清零,否則會有重複統計的情況出現第二是two和one的更新方式非常巧妙,加入one和tmp某一位都爲1,我們可以直到two的對應位應該置1,one的對應位應該置0,而如果one和tmp不同時爲1,則two應該爲0,one應該爲1,&操作和^操作的結果剛好與上述規則契合。

代碼二中也有類似情況。

完整代碼 https://github.com/Orange1991/leetcode/blob/master/137/c/solution2.c

代碼二

int singleNumber(int* nums, int numsSize) {
    int one = 0, two = 0, three = 0;
    while (numsSize) {
        // 之前已經出現1次1的位且當前數字中又爲1,則更新爲出現2次
        two |= one & *(nums + --numsSize);
        // 之前沒有出現且當前數字中爲1,更新爲出現1次
        // 或者之前出現次數爲1次,且當前數字中沒有出現,仍舊爲1次
        one ^= *(nums + numsSize);
        // 既出現了一次又出現了兩次的位即出現了3次
        three = two & one;
        // 清空出現了三次的位
        two &= ~three;
        one &= ~three;
    }
    return one;
}

完整代碼 https://github.com/Orange1991/leetcode/blob/master/137/c/main.c


2015/8/18

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章