LeetCode 89,因為題目晦澀而被點了1500+反對的搜尋問題

TechFlow2019發表於2020-08-04

本文始發於個人公眾號:TechFlow,原創不易,求個關注


今天是LeetCode專題第55篇文章,我們一起來看看LeetCode中的第89題 Gray Code(格雷碼)。

這題的官方難度是Medium,通過率是48.9%,點贊639,反對1545。又是一道反對比點贊多得多的題目,我個人發現其實這些反對很多的題目都有一個特點,就是題意比較晦澀,出題人的意圖不太容易get到。不知道是不是老外理解能力不太行,所以都給出了這麼多的反對。

我們就來看看這道題的真面目吧。

題意

題目中說gray code,格雷碼是一連串n位二進位制表示的數字。這一串的數字有一個特點就是第一個數字是0,從0開始後面的每一個數字和前一個數字只有一個二進位制位不同。

題目會給定我們一個非負整數n,要求我們生產n位的灰色程式碼,也就是產生這些數字。並且這些數字是以10進位制儲存的。

不知道大家看明白沒有,我們來看一個樣例。

樣例

Input: 2
Output: [0,1,3,2]

在上面這個例子當中,輸入是2,表示這些數字是兩位二進位制位構成的,輸出是[0, 1, 3, 2]。我們把0,1,3,2翻譯成二進位制,0是00,1是01,3是11,2是10。排列在一起的話就是00, 01, 11, 10。我們可以發現每一個數和前一個數相差的都是一個二進位制位。

題目當中相關的描述就這麼多,但其實有很多隱藏的資訊沒有給,要我們自己猜測。比如說每一個數字只能出現一次,不然的話這個序列就是無窮無盡的。另外一個隱藏資訊是,這樣的序列應該也不是唯一的,但是題目並沒有說是否所有合法的序列都可以通過測試,還是說一定要返回字典序最小的結果。

題目比較晦澀也就算了,這些隱藏資訊沒有交代清楚,也難怪大家會費解。

題解

當然以上的問題其實也不是事,我們不確定試一次也就知道了,核心還是怎麼想出解法來。

幹想是沒有結果的,還是要先分析蒐集一些資訊。首先,題目給定的n,限制了每個數能夠使用的二進位制位的數量。n個二進位制位一共能表示的數字有$2^n$種,我們無法得知是否這麼多數字都能串聯起來。假設可行的話,那麼這個問題其實就是這$2^n$個數如何擺放的問題。

所以問題的關鍵就是要尋找這樣一個序列,根據我們之前解全排列以及各種排列的方法,可以聯想得到,這大概率是一個搜尋問題。

順著搜尋的思路繼續往下,剩下的事情就容易了,我們的起始搜尋點是0。題目中要求了每兩個相鄰的數的二進位制位只相差一個,那麼我們可以遍歷這些二進位制位,尋找0的後繼節點。同樣對於每一個後繼節點來說,我們都可以用同樣的方法尋找它的後繼們。再加上gray code不能包含重複的元素,我們可以在搜尋的時候加上剪枝。

這一套其實是一個經典的搜尋問題的流程。

如果我們換個思路,雖然也能得到一樣的解法,但是思考的過程會不太一樣。怎麼換思路呢,其實也簡單,我們把它想象成一個圖論問題。也就是說,每一個數字都是圖中的一個節點。如果兩個數字之間滿足只相差了一個二進位制位,那麼說明它們之間有一條邊相連。整個問題就轉變成了我們從0這個點出發,找出所有連通的節點。

對於圖上的遍歷問題,方法就很固定了就是搜尋。也就是說從這個角度思考的話,更加容易想到搜尋上面了, 整個思考的鏈路會更短。這也是為什麼很多大神建模的時候喜歡從往圖上考慮的原因。

這些都想明白了再來寫程式碼真的就水到渠成了,整個核心程式碼真的不長:

class Solution:
    def grayCode(self, n: int) -> List[int]:
        ret = [0]
        elements = {0}
        
        def dfs(cur):
            # 遍歷與cur唯一不同的二進位制位
            for i in range(n):
                # 針對這一維做亦或,將0變1,1變0
                nxt = cur ^ (1 << i)
                if nxt in elements:
                    continue
                # 記錄答案,繼續往下遍歷
                elements.add(nxt)
                ret.append(nxt)
                dfs(nxt)
        dfs(0)
        return ret

總結

單純從思路以及最後的AC程式碼來看的話,這道題難度應該是很低的,實際上也的確如此,這題的通過率接近50%,已經是Medium難度的下屆了。但是相比於做對這題而言,更加重要的是思路。以圖論的思維來抽象建模是演算法題當中一個非常常見的手段,這是比題目本身更加寶貴的東西。

如果你讀過昨天的文章的話,會發現昨天的87題,本質上也是用的一個圖論建模的方法。但是從表現形式上來說,這兩題真的可以說是完全不一樣。建議大家能好好做做這兩題,體會一下其中思維和解法的閃光點。

今天的文章到這裡就結束了,如果喜歡本文的話,請來一波素質三連,給我一點支援吧(關注、轉發、點贊)。

本文使用 mdnice 排版

相關文章