[Leetcode]找到出現不同次數的數字(通用解法)


今天在leetcode上遇到了 137. Single Number II 這道題:

給定一個非空整數數組,除了某個元素只出現一次以外,其余每個元素均出現了三次。找出那個只出現了一次的元素。(Given a non-empty array of integers, every element appears three times except for one, which appears exactly once. Find that single one.)

Note: 你的算法應該具有線性時間復雜度。 你可以不使用額外空間來實現嗎?(our algorithm should have a linear runtime complexity. Could you implement it without using extra memory?)

Examples:
Input: [0,1,0,1,0,1,99]
Output: 99

剛開始看到這道題時候,我是略微欣喜的,因為腦子里蹦出的想法應該就是用位異或的方法解決。然而事情並沒有那么簡單。在草稿紙上模糊了快一個小時候,我點開了Discuss,進入了投票數最高的回答:

這一點開不得了,我的表情是這樣的😳(睜大雙眼的臉),里面只有這寥寥幾行代碼:

public int singleNumber(int[] A) {
    int ones = 0, twos = 0;
    for(int i = 0; i < A.length; i++){
        ones = (ones ^ A[i]) & ~twos;
        twos = (twos ^ A[i]) & ~ones;
    }
    return ones;
}

上面的代碼,如果你看兩眼就明白。你可以大大的鄙視我了,這篇文章可能不適合你,但是我們還是交個朋友吧🤝

開玩笑,言歸正傳。這時我點開了另一個回答An General Way to Handle All this sort of questions.,簡直如沐春風。該方法采用了數字邏輯電路里的計算,來解決諸如此類的問題。哈哈,好歹我本科也是學過數電這門課的。這篇回答短小精悍,我也理解了半天,下面主要介紹我的理解:


(正經臉)

這個方法核心思想是建立一個記錄狀態的變量,此方法適用於其他所有元素出現K次,求唯一一個元素出現M次的問題(every one occurs K times except one occurs M times)。對於leetcode137這個問題,K=3,M=1。

我們先討論K=2的情況,我們可以用所有元素做位異或的方法來得到只出現一次的數,那是因為出現兩次的數都通過異或把他們的所有位都置0了。對於K=2,每個數的每一位,只有兩種情況(1或0),我們可以列出這幾種情況:

current incoming next
0 0 0
0 1 1
1 0 1
1 1 0

上面的真值表對於每個數的每一位來說,由於異或具有交換律,所以我們可以看成兩兩相同的數的每一位做異或,自然都變成0。剩下一個數的每一位只能跟前面異或完的0做異或,得到的就是他本身。

現在討論K=3的情況。如果能有一種“類異或”的運算,使得3個相同的數做了這種位運算后變成0就好了,這不就跟上面的情況一樣了嘛!哈哈,請叫我們數學家。

我們要找的就是這種“類異或”的位運算。由於K=3,要進行3次位運算后這一位才為0,那么我們是不是可以把一個數的每一位看成有三個狀態呢?嗯,可以試試。

其實用三進制應該是可以解決的,但是對於代碼來說實在是不好理解。那么我們只能人為的用二進制定義每一位的這三種狀態了。根據香農第一定理,3種狀態需要兩位二進制位表示(哈哈😃,原諒我故作玄虛)。我們用(00,01,10)來分別表示每個數每一位的這三種狀態,且定義如下真值表:(該真值表的運算表示為

current(a, b) incoming(c) next(a, b)
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

來理解一下上面這個真值表,每一位用虛擬的兩位二進制(a, b)表示。假設我們把每一位的初始狀態都定為(a', b'),如果接下來進行₸運算的incoming是三個一樣的數,那么第三次₸運算后的結果必定還是(a', b')。

下面就要用到《數字邏輯電路》的知識了:根據真值表寫出邏輯式

(其實不難:對於a,把next中a=1對應的行組合選出來,對於每一個組合,凡取值為1的變量寫成原變量,取值為0的變量寫成反變量,各變量相乘后得到一個乘積項;最后,把各個組合對應的乘積項相加,就得到了相應的邏輯表達式。對於b同理)

所以:

$a = a\overline{b}\overline{c} + \overline{a}bc$
$b = \overline{a}b\overline{c} + \overline{a}\overline{b}c$

根據這兩個邏輯式寫出相應的python代碼:

a = (a&~b&~c)|(~a&b&c)
b = (~a&b&~c)|(~a&~b&c)

ps:這三種狀態我們定義00表示真實位0, 0110表示真實位1,所以有如下映射關系:

01 10 => 1
00 => 0

所以,對於最后的結果,我們只需要return a|b即可得到該位是0還是1.

最后的python代碼:

class Solution:
    def singleNumber(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        a = 0
        b = 0
        for c in nums:
            a, b = (a&~b&~c)|(~a&b&c), (~a&b&~c)|(~a&~b&c)
        return a|b

但是,上述代碼放在leetcode中,只beats掉了30%的人。幾個原因吧:

  1. 邏輯式可以化簡。
  2. 確實還有對於這道題更簡單但是不通用的方法

不過,現在你已經可以解決所有類似的題目了,驚喜吧!🇨🇳


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM