《丟雞蛋問題》重製版來襲~

2021-01-14 力扣加加


點擊藍色「力扣加加」關注我喲

加個「星標」,帶你揭開算法的神秘面紗!



題目地址(887. 雞蛋掉落)

https://leetcode-cn.com/problems/super-egg-drop/

題目描述

你將獲得  K  個雞蛋,並可以使用一棟從  1  到  N   共有 N  層樓的建築。

每個蛋的功能都是一樣的,如果一個蛋碎了,你就不能再把它掉下去。

你知道存在樓層  F ,滿足  0 <= F <= N 任何從高於 F  的樓層落下的雞蛋都會碎,從  F  樓層或比它低的樓層落下的雞蛋都不會破。

每次移動,你可以取一個雞蛋(如果你有完整的雞蛋)並把它從任一樓層  X  扔下(滿足  1 <= X <= N)。

你的目標是確切地知道 F 的值是多少。

無論 F 的初始值如何,你確定 F 的值的最小移動次數是多少?

示例 1:

輸入:K = 1, N = 2輸出:2解釋:雞蛋從 1 樓掉落。如果它碎了,我們肯定知道 F = 0 。否則,雞蛋從 2 樓掉落。如果它碎了,我們肯定知道 F = 1 。如果它沒碎,那麼我們肯定知道 F = 2 。因此,在最壞的情況下我們需要移動 2 次以確定 F 是多少。示例 2:

輸入:K = 2, N = 6輸出:3示例 3:

輸入:K = 3, N = 14輸出:4

提示:

1 <= K <= 1001 <= N <= 10000

前置知識思路

本題也是 vivo 2020 年提前批的一個筆試題。時間一個小時,一共三道題,分別是本題,合併 k 個鍊表,以及種花問題。

這道題我在很早的時候做過,也寫了題解[2]。現在看來,思路沒有講清楚。沒有講當時的思考過程還原出來,導致大家看的不太明白。今天給大家帶來的是 887.super-egg-drop 題解的「重製版」。思路更清晰,講解更透徹,如果覺得有用,那就轉發在看支持一下?OK,我們來看下這道題吧。

這道題乍一看很複雜,我們不妨從幾個簡單的例子入手,嘗試打開思路。

假如有 2 個雞蛋,6 層樓。我們應該先從哪層樓開始扔呢?想了一會,沒有什麼好的辦法。我們來考慮使用暴力的手段。

(圖 1. 這種思路是不對的)

既然我不知道先從哪層樓開始扔是最優的,那我就依次模擬從第 1,第 2。。。第 6 層扔。每一層樓丟雞蛋,都有兩種可能,碎或者不碎。由於是最壞的情況,因此我們需要模擬兩種情況,並取兩種情況中的扔次數的較大值(較大值就是最壞情況)。 然後我們從六種扔法中選擇最少次數的即可。

(圖 2. 應該是這樣的)

而每一次選擇從第幾層樓扔之後,剩下的問題似乎是一個規模變小的同樣問題。嗯哼?遞歸?

為了方便描述,我將 f(i, j) 表示有 i 個雞蛋, j 層樓,在最壞情況下,最少的次數。

偽代碼:

def superEggDrop(K, N):
    ans = N
    # 暴力枚舉從第 i 層開始扔
    for i in range(1, N + 1):
        ans = min(ans, max(self.superEggDrop(K - 1, i - 1) + 1, self.superEggDrop(K,  N - i) + 1))
    return ans

如上代碼:

self.superEggDrop(K - 1, i - 1) 指的是雞蛋破碎的情況,我們就只剩下 K - 1 個雞蛋, 並且 i - 1 個樓層需要 check。self.superEggDrop(K, N - i) + 1 指的是雞蛋沒有破碎的情況,我們仍然有 K 個雞蛋, 並且剩下 N - i 個樓層需要 check。

接下來,我們增加兩行遞歸的終止條件,這道題就完成了。

class Solution:
    def superEggDrop(self, K: int, N: int) -> int:
        if K == 1: return N
        if N == 0 or N == 1: return N
        ans = N
        # 暴力枚舉從第 i 層開始扔
        for i in range(1, N + 1):
            ans = min(ans, max(self.superEggDrop(K - 1, i - 1) + 1, self.superEggDrop(K,  N - i) + 1))
        return ans

可是如何這就結束的話,這道題也不能是 hard,而且這道題是公認難度較大的 hard 之一。

上面的代碼會 TLE,我們嘗試使用記憶化遞歸來試一下,看能不能 AC。


class Solution:
    @lru_cache()
    def superEggDrop(self, K: int, N: int) -> int:
        if K == 1: return N
        if N == 0 or N == 1: return N
        ans = N
        # 暴力枚舉從第 i 層開始扔
        for i in range(1, N + 1):
            ans = min(ans, max(self.superEggDrop(K - 1, i - 1) + 1, self.superEggDrop(K,  N - i) + 1))
        return ans

性能比剛才稍微好一點,但是還是很容易掛。

那隻好 bottom-up(動態規劃)啦。

(圖 3)

我將上面的過程簡寫成如下形式:

(圖 4)

與其遞歸地進行這個過程,我們可以使用迭代的方式。相比於上面的遞歸式,減少了棧開銷。然而兩者有著很多的相似之處。

如果說遞歸是用函數調用來模擬所有情況, 那麼動態規劃就是用表來模擬。我們知道所有的情況,無非就是 N 和 K 的所有組合,我們怎麼去枚舉 K 和 N 的所有組合?當然是套兩層循環啦!

(圖 5. 遞歸 vs 迭代)

如上,你將 dp[i][j] 看成 superEggDrop(i, j),是不是和遞歸是一摸一樣?

來看下迭代的代碼:

class Solution:
    def superEggDrop(self, K: int, N: int) -> int:
        for i in range(K + 1):
            for j in range(N + 1):
                if i == 1:
                    dp[i][j] = j
                if j == 1 or j == 0:
                    dp[i][j] == j
                dp[i][j] = j
                for k in range(1, j + 1):
                    dp[i][j] = min(dp[i][j], max(dp[i - 1][k - 1] + 1, dp[i][j - k] + 1))
        return dp[K][N]

值得注意的是,在這裡內外循環的順序無關緊要,並且內外循壞的順序對我們寫代碼來說複雜程度也是類似的,各位客官可以隨意調整內外循環的順序。比如這樣也是可以的:

class Solution:
    def superEggDrop(self, K: int, N: int) -> int:
        dp = [[0] * (K + 1) for _ in range(N + 1)]

        for i in range(N + 1):
            for j in range( K + 1):
                if j == 1:
                    dp[i][j] = i
                if i == 1 or i == 0:
                    dp[i][j] == i
                dp[i][j] = i
                for k in range(1, i + 1):
                    dp[i][j] = min(dp[i][j], max(dp[k - 1][j - 1] + 1, dp[i - k][j] + 1))
        return dp[N][K]
        dp = [[0] * (N + 1) for _ in range(K + 1)]

總結一下,上面的解題方法思路是:

然而這樣還是不能 AC。這正是這道題困難的地方。「一道題目往往有不止一種狀態轉移方程,而不同的狀態轉移方程往往性能是不同的。」

那麼這道題有沒有性能更好的其他的狀態轉移方程呢?

把思路逆轉!

這是《逆轉裁判》 中經典的臺詞, 主角在深處絕境的時候,會突然冒出這句話,從而逆轉思維,尋求突破口。

我們這樣來思考這個問題。既然題目要求最少的扔的次數,假設有一個函數 f(k, i),他的功能是求出 k 個雞蛋,扔 i 次所能檢測的最高樓層。

我們只需要不斷進行發問:

」f 函數啊 f 函數,我扔一次可以麼?「, 也就是判斷 f(k, 1) >= N 的返回值」f 函數啊 f 函數,我扔兩次呢?「, 也就是判斷 f(k, 2) >= N 的返回值」f 函數啊 f 函數,我扔 m 次呢?「, 也就是判斷 f(k, m) >= N 的返回值

我們只需要返回第一個返回值為 true 的 m 即可。

想到這裡,我條件發射地想到了二分法。聰明的小朋友們,你們覺得二分可以麼?為什麼?歡迎評論區留言討論。

那麼這個神奇的 f 函數怎麼實現呢?其實很簡單。

摔碎的情況,可以檢測的最高樓層是f(m - 1, k - 1) + 1。因為碎了嘛,我們多檢測了摔碎的這一層。沒有摔碎的情況,可以檢測的最高樓層是f(m - 1, k)。因為沒有碎,也就是說我們啥都沒檢測出來(對能檢測的最高樓層無貢獻)。

我們來看下代碼:

class Solution:
    def superEggDrop(self, K: int, N: int) -> int:
        def f(m, k):
            if k == 0 or m == 0: return 0
            return f(m - 1, k - 1) + 1 +  f(m - 1, k)
        m = 0
        while f(m, K) < N:
            m += 1
        return m

上面的代碼可以 AC。我們來順手優化成迭代式。

class Solution:
    def superEggDrop(self, K: int, N: int) -> int:
        dp = [[0] * (K + 1) for _ in range(N + 1)]
        m = 0
        while dp[m][K] < N:
            m += 1
            for i in range(1, K + 1):
                dp[m][i] = dp[m - 1][i - 1] + 1 + dp[m - 1][i]
        return m

代碼

代碼支持:JavaSCript,Python

Python:

class Solution:
    def superEggDrop(self, K: int, N: int) -> int:
        dp = [[0] * (K + 1) for _ in range(N + 1)]
        m = 0
        while dp[m][K] < N:
            m += 1
            for i in range(1, K + 1):
                dp[m][i] = dp[m - 1][i - 1] + 1 + dp[m - 1][i]
        return m

JavaSCript:

var superEggDrop = function (K, N) {
  // 不選擇dp[K][M]的原因是dp[M][K]可以簡化操作
  const dp = Array(N + 1)
    .fill(0)
    .map((_) => Array(K + 1).fill(0));

  let m = 0;
  while (dp[m][K] < N) {
    m++;
    for (let k = 1; k <= K; ++k) dp[m][k] = dp[m - 1][k - 1] + 1 + dp[m - 1][k];
  }
  return m;
};

「複雜度分析」

時間複雜度:$O(m * K)$,其中 m 為答案。總結如果你還不熟悉動態規劃,可以先從遞歸做起。多畫圖,當你做多了題之後,就會越來越從容。對於動態規劃問題,往往有不止一種狀態轉移方程,而不同的狀態轉移方程往往性能是不同的。

更多題解可以訪問我的 LeetCode 題解倉庫:https://github.com/azl397985856/leetcode 。目前已經 30K star 啦。

大家也可以關注我的公眾號《力扣加加》獲取更多更新鮮的 LeetCode 題解

Reference[1]

動態規劃: https://github.com/azl397985856/leetcode/blob/master/thinkings/dynamic-programming.md

[2]

887.super-egg-drop 題解: https://github.com/azl397985856/leetcode/blob/master/problems/887.super-egg-drop.md

相關焦點

  • 對《丟雞蛋問題》的一點補充
    我們會對大家提出的問題進行篩選,將有意義的問題開放出來給大家討論和學習。本次給大家帶來的/是【異議!】系列「第二彈」。原題地址:https://leetcode-cn.com/problems/super-egg-drop/事情的起源昨天有人在我的力扣題解下留言,問我《丟雞蛋問題》重製版來襲~》題解中為什麼第二種算法是加法而不是 min 什麼的。
  • 重製版《Vertigo 2》VR遊戲即將來襲,可免費升級
    12月30日青亭網報導,近期VR遊戲《Vertigo》開發者Zach Tsiakalis-Brown表示,《Vertigo》重製版即將來襲,並放出一段視頻。據了解,《Vertigo Remaster》重製版是對2016年份發行的《Vertigo》的重新設計,並利用大量現代化技術,遊戲中加入大量新關卡、敵人和boss。除此之外,Tsiakalis-Brown還表示:現有的《Vertigo》玩家可以免費升級《Vertigo Remaster》,目前該作在Steam售價為48元。
  • 一拳超人重製版第180話:怪人餓狼來襲,目標直指神級怪人!
    《一拳超人》重製版漫畫最新一話已經更新了,今天我們就來簡單的分析一下本話的主要內容吧! 主要內容1:怪人餓狼來襲!這就是絕對惡麼? 截止至重製版的最新一話,已經有多位怪人提到了「神級怪人」的領域! 那麼問題來了,為何只有怪人提到這個信息呢?這是否暗示了地球上的怪人皆被「神級怪人」掌控著呢? 至於普通怪人和神級怪人的聯繫究竟是什麼,這一點還需要ONE先生給我們答案!
  • 《最終幻想7重製版》結局解讀:一些問題的答案和理論
    《最終幻想7重製版》結局解讀:一些問題的答案和理論 《最終幻想7》重製版的結局出乎意料,有點讓人困惑,而且非常、非常出乎意料。
  • samsara room重製版攻略合集 輪迴的房間重製版怎麼玩
    導 讀 輪迴的房間重製版攻略大全。
  • 《使命召喚4重製版》讀條慢怎麼辦
    導 讀 使命召喚4重製版讀條慢怎麼辦?今天小編就為大家帶來使命召喚4重製版讀條時間過長解決方法,讓我們來一起看看吧!
  • 《上古捲軸5:天際重製版》1.2版補丁公布
    根據B社的消息,《上古捲軸5:天際重製版》的1.2版本,並沒有增加任何的新特性,但是解決了從1.1版本一來存在的很多問題,並解決了遊戲中的一些表現問題。根據B社的消息,《上古捲軸5:天際重製版(Skyrim Special Edition)》的1.2版本,並沒有增加任何的新特性,但是解決了從1.1版本一來存在的很多問題,並解決了遊戲中的一些表現問題。此外,該補丁還帶來了針對Mod瀏覽器的改進。
  • 《生化危機2:重製版》製作人採訪:重製3代不是不可能
    《生化危機2:重製版》目前已經正式發售,而且遊戲獲得了來自媒體和玩家的一致好評,同時製作人平林良章來到了臺北電玩展現場,我們也與友媒一起對他進行了採訪。而他也在採訪中談到,雖然目前他們沒有重製3代作品的打算,但如果遊戲在玩家們之間的反響出色,那麼這也是可能的。
  • 《劍網3》重製版新校服原畫連載 全門派氣場飆升
    《劍網3》重製版新校服原畫連載 全門派氣場飆升 時間:2017-12-23 09:37:48
  • 《翱翔機翼:重製版》免安裝綠色漢化版下載發布
    點擊進入《翱翔機翼:重製版》免安裝綠色漢化版下載下載地址遊戲名稱:翱翔機翼:重製版英文名稱:Wings!Remastered Edition遊戲類型:飛行遊戲FLY製作公司:Cinemaware 發行公司:Cinemaware 遊戲平臺:PC語言版本:中文發售日期:2014-10-17遊戲專題:http://www.ali213.net/zt/【遊戲介紹】  《翱翔機翼:重製版
  • 生化危機3重製版
    生化危機3重製版已經上線了,這個重製版獲得不少好評,遊戲質量很不錯,玩家紛紛表示遊戲時間太短了,相信很多玩家都在體驗了吧,這裡為大家帶來第八章的流程,那麼第八章怎麼玩?想來很多朋友都還不是很清楚吧,所以呢小編今天給大家帶來的就是生化危機3重製版醫院地圖圖文攻略。
  • 秒殺重製版!《上古捲軸5》PC版大師級MOD截圖
    秒殺重製版!《上古捲軸5》PC版大師級MOD截圖 時間:2016-06-24 08:00:06 來源:3DM新聞組-skylark
  • 暴雪:我們還沒放棄《魔獸爭霸3:重製版》
    近期上市的《魔獸爭霸3:重製版》不僅辜負了玩家的對於暴雪的信任,也極大程度上損毀了「暴雪出品,必屬精品」這個金子招牌。在2月6日的投資者電話會議上,暴雪承認,《魔獸爭霸3:重製版》發售是困難的一周,但他們並沒有打算就此放棄。
  • 《星際爭霸》重製版已登陸國服暴雪戰網
    穿越20年的暴雪即時戰略(RTS)經典之作《星際爭霸:重製版》從8月30日起將正式入駐國服暴雪戰網。還記得曾經的宿舍大戰嗎?還記得戰役裡波瀾壯闊的艾爾之戰嗎?還記得飛龍甩尾、矩陣閃電這樣的極限操作嗎?《星際爭霸》作為一款誕生於20年前的經典之作,承載了那個時代關於遊戲、電競太多的回憶。20年過去,《星際爭霸》重製歸來,再次將我們帶回那個恢弘史詩的太空世界。
  • 《星際爭霸》重製版同步國服?官方:只是BUG
    重製版遊戲還將更新了對話和語音,增加了暴雪好友與配對系統(包括區域網+戰網),對於星際粉絲們可謂一大好消息。昨日消息,暴雪正式放出了《星際爭霸:重製版》的預告片,同時在戰網上開啟了遊戲的預購,定價14.99美元,約合人民幣101元,8月14日正式發售。
  • 《生化3重製版》將擴展主角陣容 獵殺者兇殘回歸!
    《生化危機3:重製版》的公布讓無數粉絲為之激動。近日,日本卡普空TV推出了最新一期的節目,其中人們向卡普空的官方人士提出了幾個關於《生化危機3:重製版》的問題,從而透露了一些遊戲的情報,一起來了解下吧!
  • 《生化危機3重製版》 試玩版劇情流程簡介 試玩體驗分享
    今天我們給大家帶來了生化危機3重製版試玩版劇情流程簡介和生化3re試玩體驗分享,相信大家對卡普空這款冷飯大作應該非常期待,遊戲即將發售,感興趣的小夥伴一起來看下吧。 今天我們給大家帶來了生化危機3重製版試玩版劇情流程簡介和生化3re試玩體驗分享,相信大家對卡普空這款冷飯大作應該非常期待
  • 《FF10:HD重製版》PC版60幀MOD測試 急需改進
    SE社旗下《最終幻想10:HD重製版》PC版現已上市,新功能包括比如改編BGM、收錄國際版內容支持自動存檔和5個遊戲加速狀態,包括高速模式和無遭遇戰模式,人物、環境高清建模等。
  • 白日噩夢:民間版《生化危機》,像極了低配版《生化2重製版》
    本文原創侵權必究白日噩夢:民間版《生化危機》,像極了低配版《生化2重製版》《生化危機2重製版》可以說是今年最香的一碗冷飯,在此之前可把廣大的「生化迷」給急瘋了。其中就有一個義大利的民營團隊,高調宣布將以免費的形式用虛幻4引擎去重製《生化2》,可最後還是被卡普空主動叫停了他們項目的開發,也因此《生化危機2重製版》正式立項。可實際上這個義大利的小作坊並沒有就此罷手,他們在已經完成內容的基礎上進行了豐富和修改,最後做出了一款與《生化危機》玩法類似卻有著自己獨立劇情的恐怖冒險遊戲《白日噩夢:1998Daymare:1998)》。
  • 《月姬》重製版今夏發售!不知不覺等了12年!
    作為型月GALGAME起點的《月姬》重製版終於有了正式的發售日期!《月姫 -A piece of blue glass moon-》將於今年夏季登陸PS4與NS平臺。《月姬》完全版於2000年12月29日的C59上發售的一款R18 GALGAME,擁有5條主線、9個結局。