原題地址
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