利用位操作進行特殊數組的查找-leetcode

只出現一次的數字

原題:136. 只出現一次的數字

題目描述

給定一個非空整數數組,除了某個元素只出現一次以外,其餘每個元素均出現兩次。找出那個只出現了一次的元素。
說明:
你的算法應該具有線性時間複雜度。 你可以不使用額外空間來實現嗎?

分析

利用兩個相同正數異或抵消,以及異或具有交換律的性質,數組裏所有數字全部異或,單一的數字便會被剩下。時間複雜度爲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 \star\star

原題:137. 只出現一次的數字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 = 11 # 1 = 22 # 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=1ones=0 );
    • threes = ones & twos:記錄至目前元素num,二進制某位出現 3 次(即當 onestwos 對應位同時爲 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,出現 3N3N+2次的位爲 ones=0。由於 three 其實是 ones & twos 的結果,因此我們可以捨棄 threes,僅使用 onestwos 來記錄出現次數。
某位出現 1次 2次 3次 4次 5次 6次
ones 1 0 0 1 0 0
twos 0 1 0 0 1 0
threes 0 0 1 0 0 1
  • 代碼分析:
    • 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 不變。
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

原題:260. 只出現一次的數字 III

題目描述

給定一個整數數組 nums,其中恰好有兩個元素只出現一次,其餘所有元素均出現兩次。 找出只出現一次的那兩個元素。

示例 : 輸入: [1,2,1,3,2,5] 輸出: [3,5]

注意:
結果輸出的順序並不重要,對於上面的例子, [5, 3] 也是正確答案。
你的算法應該具有線性時間複雜度。你能否僅使用常數空間複雜度來實現?

分析

我們可以利用雙層循環以O(n2n^2)的時間複雜度,O(1)的空間複雜度來解決,但是不符合要求;當然我們也可以利用Hash字典數據結構在O(n)的時間複雜度,O(n)的空間複雜度來解決,但是依然不符合要求;

於是我們根據163題中異或的思想來解決:

  1. 全部元素異或消掉出現兩次的數字. 異或的結果爲s.
  2. 尋找slowbit值. lowbit(s)s二進制表達式中最右邊的1所對應的值. 因此lowbit(s)二進制表達式中只有一個bit 1.
    lowbit(s) = s & -s
    例如: s=1010
    lowbit(s) = 1010 & 0110 = 0010 = 2
  3. lowbit(s)將數組分成兩組. 一組中,元素A[i] & lowbit(s) == lowbit(s), 即包含lowbit(s)的bit 1. 剩餘的是另一組.
    而且,兩個不同數也一定分在不同組. 因爲異或值s中的bit1就是因爲兩個數字的不同而貢獻的.
  4. 同一組的元素再異或求出不同數字. 出現兩次的數字, 肯定出現同一組, 異或後消除掉.也就是說結果二進制只存在一個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

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