只出現一次的數字
題目描述
給定一個非空整數數組,除了某個元素只出現一次以外,其餘每個元素均出現兩次。找出那個只出現了一次的元素。
說明:
你的算法應該具有線性
時間複雜度。 你可以不使用額外空間
來實現嗎?
分析
利用兩個相同正數異或抵消
,以及異或具有交換律
的性質,數組裏所有數字全部異或,單一的數字
便會被剩下。時間複雜度爲O(n)
,空間複雜度爲O(1)
。
代碼
class Solution(object):
def singleNumber(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
res = 0
for i in nums:
res ^= i
return res
只出現一次的數字 II
題目描述
給定一個非空整數數組
,除了某個元素只出現一次以外,其餘每個元素均出現了三次。找出那個只出現了一次的元素。
說明:
你的算法應該具有線性
時間複雜度。 你可以不使用額外空間
來實現嗎?
分析及代碼
(算法借鑑於:)
二進制下不考慮進位的加法
:本題爲136的拓展,136 題中我們用到了異或運算。實際上,異或運算的含義
是二進制下不考慮進位的加法
,即:0 xor 0=0+0=0
,0 xor 1=0+1=1
,1 xor 0=1+0=1
,1 xor 1=1+1=0(不進位)
。三進制下不考慮進位的加法
:通過定義某種運算#
,使得0 # 1 = 1
,1 # 1 = 2
,2 # 1 = 0
。在此運算規則下,出現了3
次的數字的二進制所有位全部抵消爲0
,而留下只出現1
次的數字二進制對應位爲1
。因此,在此運算規則下將整個arr
中數字遍歷加和,留下來的結果則爲只出現1
次的數字。- 代碼分析:
ones ^= num
:記錄至目前元素num
,二進制某位出現 1 次(當某位出現 3 次時有ones=1
,與twos = 1
共同表示“出現3
次”);twos |= ones & num
:記錄至目前元素num
,二進制某位出現2
次 (當某位出現2
次時,twos=1
且ones=0
);threes = ones & twos
:記錄至目前元素num
,二進制某位出現3
次(即當ones
和twos
對應位同時爲 1 時three=1
)。one &= ~threes, two &= ~threes
:將 ones, twos 中出現了 3 次的對應位清零,實現 “不考慮進位的三進制加法” 。
class Solution:
def singleNumber(self, nums: [int]) -> int:
ones, twos, threes = 0, 0, 0
for num in nums:
twos |= ones & num # 二進制某位出現1次時twos = 0,出現2, 3次時twos = 1;
ones ^= num # 二進制某位出現2次時ones = 0,出現1, 3次時ones = 1;
threes = ones & twos # 二進制某位出現3次時(即twos = ones = 1時)three = 1,其餘即出現1, 2次時three = 0;
ones &= ~threes # 將二進制下出現3次的位置零,實現`三進制下不考慮進位的加法`;
twos &= ~threes
return ones
- 進一步簡化:以上過程本質上是通過構建 3 個變量的狀態轉換表來表示對應位的出現次數:使所有數字“相加”後出現
3N+1
次的位ones = 1
,出現3N
,3N+2
次的位爲ones=0
。由於three
其實是ones & twos
的結果,因此我們可以捨棄threes
,僅使用ones
和twos
來記錄出現次數。
某位出現 | 1次 | 2次 | 3次 | 4次 | 5次 | 6次 | … |
---|---|---|---|---|---|---|---|
ones | 1 | 0 | 0 | 1 | 0 | 0 | … |
twos | 0 | 1 | 0 | 0 | 1 | 0 | … |
threes | … |
- 代碼分析:
- ones = ones ^ num & ~twos:
- 當 num = 1 時,只當 ones=twos=0 時將 ones 置 1,代表出現 3N+1 次;其餘置 0,根據 twos 值分別代表出現 3N 次和 3N+2 次;
- 當 num=0 時,ones 不變;
- twos = twos ^ num & ~ones:
- 當 num = 1 時,只當 ones = twos = 0 時將twos 置 1,代表出現 3N+2 次;其餘置 0,根據 ones 值分別代表出現 3N 次和 3N+1 次。
- 當 num=0 時,twos 不變。
- ones = ones ^ num & ~twos:
class Solution:
def singleNumber(self, nums: [int]) -> int:
ones, twos = 0, 0 #出現一次的位,和兩次的位
for num in nums:
ones = ones ^ num & ~twos #既不在出現一次的ones,也不在出現兩次的twos裏面,我們就記錄下來,出現了一次,再次出現則會抵消
twos = twos ^ num & ~ones #既不在出現兩次的twos裏面,也不再出現一次的ones裏面(不止一次了),記錄出現兩次,第三次則會抵消
return ones
只出現一次的數字 III
題目描述
給定一個整數數組 nums
,其中恰好有兩個元素只出現一次
,其餘所有元素均出現兩次。 找出只出現一次的那兩個元素。
示例 : 輸入: [1,2,1,3,2,5] 輸出: [3,5]
注意:
結果輸出的順序並不重要,對於上面的例子, [5, 3] 也是正確答案。
你的算法應該具有線性時間複雜度。你能否僅使用常數空間複雜度來實現?
分析
我們可以利用雙層循環以O()的時間複雜度,O(1)的空間複雜度來解決,但是不符合要求;當然我們也可以利用Hash字典數據結構在O(n)的時間複雜度,O(n)的空間複雜度來解決,但是依然不符合要求;
於是我們根據163題中異或的思想來解決:
- 全部元素異或消掉出現兩次的數字. 異或的結果爲
s
. - 尋找
s
的lowbit
值.lowbit(s)
爲s
的二進制表達式中最右邊的1所對應的值
. 因此lowbit(s)
二進制表達式中只有一個bit 1
.
lowbit(s) = s & -s
例如: s=1010
lowbit(s) = 1010 & 0110 = 0010 = 2
- 用
lowbit(s)
將數組分成兩組. 一組中,元素A[i] & lowbit(s) == lowbit(s)
, 即包含lowbit(s)的bit 1
. 剩餘的是另一組.
而且,兩個不同數也一定分在不同組
. 因爲異或值s
中的bit1
就是因爲兩個數字的不同而貢獻的. 同一組的元素再異或求出不同數字
. 出現兩次的數字
, 肯定出現同一組
,異或後消除掉
.也就是說結果二進制只存在一個1
,就是n
最低位的1
.
代碼
class Solution(object):
def singleNumber(self, nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
s = 0
for i in nums:
s ^= i
mask = s&(-s)
res1 = 0
res2 = 0
for i in nums:
if i&mask == mask:
res1 ^= i
else:
res2 ^= i
return res1,res2