leetcode雞蛋掉落問題(egg drop)

2021-02-20 搬磚糊牆小碼農

本來看算法4看得好好的,結果有一個作業提到了這個雞蛋掉落問題,實在是想不通啊otz。

搜索後發現是leetcode上的一道經典面試題~因為過於經典,已經被踢出google面試題庫了(。)

那我們就直接看看leetcode上的題目叭!

leetcode現在有中文站,看起來更方便了:https://leetcode-cn.com/problems/super-egg-drop/solution/

--

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

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

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

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

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

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

--

看題目第一反應就是"二分查找"。

但是細想就覺得不對。

舉個慄子:

如果有2顆蛋,100層樓。

用二分法,第一次從50層扔,蛋碎了。第二次從25層扔,蛋又碎了。

蛋碎光了,我們無法知道F是第幾層了。

雞蛋碎了就減少了一次機會,這點特別重要!!

手上只有一個雞蛋的時候,只能從第一層,一層層的爬樓扔,才能知道F層。如果F是49層,總共就要扔50次?

50次也太多了,這種算法題肯定有更少次數的方法_(:з」∠)_

---

咋樣才能確定F層呢?

1.<=F層的不會破

2.>F層的會破

只有找到了破與不破的邊界,才能找到F層~那就倒著想~

比如一共扔了x次才找到了F層。

扔第x次和第n(1<=n<x-1)次的時候,分別是在F層和F+1層扔的(順序可以打亂)~

兩次扔雞蛋的層數相鄰,才能找到F層嘛。

由此可知,測出F層的方法和雞蛋數、扔的次數、在哪一層扔的都有關。

在儘可能少的雞蛋和儘可能少的次數前提下,咋確定F層捏?

如果我們是歐皇,就說明我們第一次扔到了F層~可以少走很多彎路~一顆蛋+2次就能確認F層:

第一次湊巧扔在了F層,沒碎。

第二次因為只有一顆蛋,怕的一批,只敢先扔在了F+1層,碎了。

第一次扔的樓層就是F層。問題結束。

但我們不是歐皇。

非酋的我們,只能考慮在所有情況下,怎麼扔才能次數最少的確認F層?

F層的值在雞蛋數和樓層數固定的情況下是可變的,所以不能用:

K個雞蛋在N樓中求出F層

「fun(雞蛋數K,樓層數N)=F層的值」 

來解題。

但在雞蛋數和樓層數固定的情況下,確定F層用的最小次數是恆定的!

這就是這題的真實問題~

於是問題解析為:

K個雞蛋在[1, N]層中最少扔多少次才能求出F層

「fun(雞蛋數K,樓層數N)=測出F層的最少扔的次數」

題目終於理解清白了,開始正式做題~

不知道咋搞的時候,就設置一個未知數想想~

按照直觀思路,一次次的拋雞蛋試試~

前提:K個雞蛋,N層樓,K個雞蛋在[1, N]層中最少扔t次才能求出F層

問題:求t

我們在x層丟了第一個雞蛋,有兩種後果:

蛋碎了,我們還有K-1個雞蛋,問題變為K-1個蛋在[1, x-1]層中最少扔n1次才能求出F層;

蛋沒碎,我們還有K個雞蛋,問題變為K個蛋在[x+1, N]層中最少扔n2次才能求出F層。

找到n1和n2中最大的數字:max(n1, n2),再加上第一次扔的次數1,即為t。

fun(雞蛋數K,樓層數N) = 1 + max(n1, n2)

又因為:

n1 = fun(雞蛋數K-1,樓層數x-1)

n2 = fun(雞蛋數K,樓層數N-x)

轉換為:

fun(雞蛋數K,樓層數N) = 1 + max(fun(雞蛋數K-1,樓層數x-1),  fun(雞蛋數K,樓層數N-x))

哎呀,這不是遞歸嗎~

一個大問題拆分為了一個個小問題~


from functools import lru_cache
class RecursionSuperEgg(object):    '''遞歸方法求雞蛋掉落問題'''             @lru_cache(maxsize=None) def superEggDrop(self, egg: int, floor: int) -> int: if egg == 0: min_times = 0         elif egg == 1: min_times = floor elif floor == 0: min_times = 0 else: min_times_list = list()             for current_floor in range(1, floor + 1): one_min_times = 1 + max(self.superEggDrop(egg - 1, current_floor - 1), self.superEggDrop(egg, floor - current_floor)) min_times_list.append(one_min_times) min_times = min(min_times_list)
return min_times
if __name__ == "__main__": recursion_super_egg = RecursionSuperEgg() print(recursion_super_egg.superEggDrop(2, 100))    

問題解決啦~

2蛋100樓,最多丟14次就能得出F層~

----

但是這種遍歷每一層樓的遞歸太佔資源了,leetcode不讓我們這樣搞。

我們得想想幾個辦法:

還是用遞歸,想辦法不遍歷每一層樓,也能求出最小次數

不用遞歸,用船新的方法~

---

先來看看遞歸能不能改善:

之前的for循環是這樣寫的:

for current_floor in range(1, floor + 1):    one_min_times = 1 + max(self.superEggDrop(egg - 1, current_floor - 1), self.superEggDrop(egg, floor - current_floor))

1<=current_floor<=floor

可以用二分法~

先讓current_floor取int((1+floor)/2)(這裡隨便向上or向下取整);

比較碎了和沒碎兩種情況中,哪一種的次數多:

2.1如果碎了的最多次數是n1:

for current_floor in range(1, int((1+floor)/2)):

        2.2如果沒碎的最多次數是n2:

for current_floor in range(int((1+floor)/2), floor):

比較這兩者中次數最多的,就是此雞蛋數+此樓層數在int((1+floor)/2)層時的最多次數啦~:

fun(雞蛋數K,樓層數N) = 1 + max(n1, n2)

就這樣一次次二分查找,就能把遍歷範圍從[1, floor]的floor個變為:

floor/2個

floor/4個


直到個數變為2個,就不能再用二分查找了,但是這時候也把floor次的遍歷次數減少了很多很多了~

比如100層樓~,正常需要遍歷100遍~

二分查找的話,最多只用遍歷6遍~:

50 25 13 6 3 1

大大減少了調用遞歸函數的次數!

代碼搞起來~

class Solution(object):    @lru_cache(maxsize=None)    def superEggDrop(self, K: int, N: int) -> int:                memo = {}        def dp(k, n):                        if (k, n) not in memo:                                if n == 0:                    ans = 0                                elif k == 1:                    ans = n                                else:                    lo, hi = 1, n                                        while lo + 1 < hi:                        x = (lo + hi) // 2                                                t1 = dp(k-1, x-1)                                                t2 = dp(k, n-x)
if t1 < t2: lo = x elif t1 > t2: hi = x else: lo = hi = x print(lo, hi) ans = 1 + min(max(dp(k-1, x-1), dp(k, n-x)) for x in (lo, hi))
memo[(k, n)] = ans return memo[k, n]
return dp(K, N)

---

其實題在這裡就結束啦~

但是還有個有趣的規律,有興趣的可以繼續看看~

我們用枚舉法記錄一下2顆蛋100層的情況,第一次選擇第1層,第2層,第3層....第100層扔蛋的最小次數~

class Solution_time_limit_exceeded(object):    '''    Time Limit Exceeded    '''    def superEggDrop(self, egg: int, floor: int) -> int:                self.first_floor_times_dict = dict()
cache_egg_and_floor = [[0]*(egg+1) for floor in range(floor+1)]

def each_min_times(egg, floor): if egg == 0: return 0 elif egg == 1: return floor
if floor == 0: return 0 elif floor == 1: return 1


if not cache_egg_and_floor[floor][egg]: all_floor_max_times_list = list() for one_floor in range(1, floor+1): max_times = max(each_min_times(egg-1, one_floor-1), each_min_times(egg, floor-one_floor)) + 1 all_floor_max_times_list.append(max_times) self.first_floor_times_dict[one_floor] = max_times
all_min_times = min(all_floor_max_times_list) if all_min_times >= floor: all_min_times = floor
cache_egg_and_floor[floor][egg] = all_min_times
return cache_egg_and_floor[floor][egg]

return each_min_times(egg, floor)
def run(self): self.superEggDrop(2, 100) print(self.first_floor_times_dict)

答案是:

 {1: 15, 2: 15, 3: 15, 4: 15, 5: 15, 6: 15, 7: 15, 8: 15, 9: 14, 10: 14, 11: 14, 12: 14, 13: 14, 14: 14, 15: 15, 16: 16, 17: 17, 18: 18, 19: 19, 20: 20, 21: 21, 22: 22, 23: 23, 24: 24, 25: 25, 26: 26, 27: 27, 28: 28, 29: 29, 30: 30, 31: 31, 32: 32, 33: 33, 34: 34, 35: 35, 36: 36, 37: 37, 38: 38, 39: 39, 40: 40, 41: 41, 42: 42, 43: 43, 44: 44, 45: 45, 46: 46, 47: 47, 48: 48, 49: 49, 50: 50, 51: 51, 52: 52, 53: 53, 54: 54, 55: 55, 56: 56, 57: 57, 58: 58, 59: 59, 60: 60, 61: 61, 62: 62, 63: 63, 64: 64, 65: 65, 66: 66, 67: 67, 68: 68, 69: 69, 70: 70, 71: 71, 72: 72, 73: 73, 74: 74, 75: 75, 76: 76, 77: 77, 78: 78, 79: 79, 80: 80, 81: 81, 82: 82, 83: 83, 84: 84, 85: 85, 86: 86, 87: 87, 88: 88, 89: 89, 90: 90, 91: 91, 92: 92, 93: 93, 94: 94, 95: 95, 96: 96, 97: 97, 98: 98, 99: 99, 100: 100}

第一次在第1層扔時,最小次數為15

第一次在第2層扔時,最小次數為15

...

第一次在第9層扔時,最小次數為14

...

第一次在第14層扔時,最小次數為14

第一次在第15層扔時,最小次數為15

...

可以觀察到,從9-14層扔下去,最小次數都是14~也是這題的答案~

那麼是不是總有個臨界值,第一次在第x層扔時,最小次數為x,同時這個x層也是F層?

因為從第一次從第1層~第N層,最小次數是先減少後增加的,所以一定會有:

第一次在第x層扔時,最小次數為x,同時這個x層也是F層

這種情況出現!

題目再次轉換為:

求出最小次數,就等於求出了F的值~

class RecursionSuperEgg(object):    @lru_cache(maxsize=None)    def superEggDrop(self, egg: int, floor: int) -> int:        if egg == 0:            min_times = 0        elif egg == 1:            min_times = floor        elif floor == 0:            min_times = 0        else:            min_times_list = list()            for current_floor in range(1, floor + 1):                one_min_times = 1 + max(self.superEggDrop(egg - 1, current_floor - 1), self.superEggDrop(egg, floor - current_floor))                min_times_list.append(one_min_times)            min_times = min(min_times_list)
return min_times

好了,這題真的做完啦~

是不是很赤幾~

相關焦點

  • 《丟雞蛋問題》重製版來襲~
    雞蛋掉落)https://leetcode-cn.com/problems/super-egg-drop/題目描述你將獲得  K  個雞蛋,並可以使用一棟從  1  到  N   共有 N  層樓的建築。
  • 對《丟雞蛋問題》的一點補充
    我們會對大家提出的問題進行篩選,將有意義的問題開放出來給大家討論和學習。本次給大家帶來的/是【異議!】系列「第二彈」。原題地址:https://leetcode-cn.com/problems/super-egg-drop/事情的起源昨天有人在我的力扣題解下留言,問我《丟雞蛋問題》重製版來襲~》題解中為什麼第二種算法是加法而不是 min 什麼的。
  • egg的意思是雞蛋,你知道a good egg是什麼意思嗎?
    說到egg這個單詞,很容易想到雞蛋這個意思,除了這個意思你還知道其它意思嗎?今天,我們就一起來看一下egg這個單詞。首先,egg可以做名詞,意思是卵、雞蛋等。這句話中的egg意思是卵,是可數名詞。2、Bind the mixture together with a little beaten egg.用少許打過的蛋將混合料攪拌在一起。這句話中的egg意思是雞蛋,通常指用作食物的禽類的蛋。3、The male sperm fertilizes the female egg.雄性的精子使雌性的卵子受精。
  • egg是雞蛋,apple是蘋果,那「egg apple」是什麼意思呢?猜不到!
    下面我們就具體來看看吧~egg appleegg apple不是雞蛋蘋果,再說也沒有這樣的蘋果啊~所以egg apple是一種形象的比喻說法,意思是指「茄子」。在英語裡,茄子最常見的表達就是eggplant和egg apple,在英式英語裡則是aubergine。
  • 「詞彙擴充」雞蛋是egg,那蛋黃、蛋白用英語怎麼說呢?
    雞蛋是我們生活中再熟悉不過的食物了,我們都知道雞蛋的英文是egg, 可如果你想說我只喜歡吃蛋清不喜歡吃蛋黃,是不是很抓耳撓腮.?沒關係,今天就帶大家學習這兩個詞彙。【蛋黃】Egg Yolk(可數名詞)複數形式: egg yolks英英釋義: Egg yolks are the yellow part of an egg.
  • 漲知識了丨「蛋白」是egg white!「蛋黃」是egg yellow嗎?
    張文宏醫生提醒大家特殊期間要加強營養,張醫生建議大家每天早上食用牛奶雞蛋三明治,每天一個雞蛋想必是很多小夥伴們必備的營養早餐之一,那麼「煮蛋」,「炒蛋」還有「蛋白」,「蛋黃」等都該怎麼用英語表達呢?所有跟雞蛋有關的單詞全在今天的文章裡啦!
  • 「egg」是雞蛋,「plant」是植物,但是「eggplant」是個啥?
    找出你的問題,但要把所有力量和精力都放在解決方法上。你會在什麼時候感覺到自己英文詞彙匱乏呢?對梨子來說,大概就是逛菜市場的時候。雖然知道蔬菜是vegetable,但是菜市場裡賣的那些我們平時吃的菜,好多根本都叫不出英文名!每次這個時候,都會忍不住懷疑自己是個假的英語博主。
  • 「蛋白」是egg white,那「蛋黃」用英語怎麼說呢?
    所有跟雞蛋有關的單詞全在今天的文章裡!   首先我們來認識一下雞蛋的各個部分    Eggshell 蛋殼   eggshell (noun):    the thin, hard, outer layer of an egg.
  • 雙語閱讀:a carrot, an egg or a coffee bean?(深度哲理)
    當她剛解決了一個問題,似乎又出現了另一個新的問題。她媽媽把她帶去廚房,把三個罐子裝滿了水,然後把它們都放在很旺的爐火上,水很快就沸騰了。She then asked her to take an egg and break it.「胡蘿蔔,雞蛋和咖啡,」年輕的女人回答。母親把她拉得更近,讓她摸一摸胡蘿蔔。她注意到它們變軟了,然後母親讓她拿一個雞蛋剝開。
  • Egg的幾種英語表達,你確定你知道嘛?
    ↑↑↑如果雞蛋看起來像這樣。Then they are called 「Sunny side up」.Over easy↑↑↑The egg just been flipped for a short time.雞蛋只翻面兒煎了一會兒。
  • 認識雞蛋,炒雞蛋,老少皆宜的雞蛋場景英語和lapbook來一波
    Can be white or brown, depending on the breed of hen; the nutritional value of the egg is the same. Approximately 10,000 tiny pores allow moisture and gases in and out.蛋殼:雞蛋抵禦細菌入侵的第一道防線。
  • 英語學習提升—英語詞彙學習:關於「egg」延伸,你了解多少?
    字典上的解釋是「(鳥類的)蛋;(魚、昆蟲等的)卵;(用作食物的)禽蛋;(尤指)雞蛋;卵子;卵細胞。其實egg除了作為名字外,還可以做動詞,英文釋義是:to throw eggs at somebody or something, especially to express disapproval;to incite somebody to do something, 即朝…扔雞蛋;慫恿某人幹某事;用法是 egg somebody on.
  • 「雞蛋雙面煎」(荷包蛋)用英語怎麼說?
    「雞蛋雙面煎」(荷包蛋)用英語怎麼說?希望你能把自己訓練出用英語學英語,用英語理解英語的能力。用英語學英語,意味著你「輸入」的語言是英語,同時你「輸出」的語言也是英語。當你「輸出」的語言是英語時,你就是在解決一個問題:學英語就是為了用。
  • 熱門事件學英語:「假雞蛋」英語怎麼說
    山東煙臺市民前兩天在小區商店裡買了2斤雞蛋,裡面竟摻了若干假雞蛋。煮熟後,蛋黃竟能從地上彈起20釐米。   假雞蛋有人稱其為「人造蛋」, 或者叫「橡皮雞蛋」。據資料介紹是由碳酸鈣、石膏以及海藻酸鈉、明礬、明膠、色素等化工原料經人工合成製造出來的。
  • 入門|egg.js 入門之egg-jwt
    小小繼續學習,這次學習的內容是egg-jwt 相關。創建egg項目這裡創建一個egg新項目,這裡使用的是ts模式。npm init egg --type=tsnpm install安裝相關的包這裡創建並安裝完成以後,需要再次初始化倆包,分別為egg-cors與egg-jwt token 生成的驗證包npm install egg-cors egg-jwt --save配置相關插件這裡配置相關的插件import { EggPlugin } from
  • 學會點一份完美的雞蛋讓女朋友喜上眉梢
    承接之前一期的煎雞蛋,本期小編帶領大家再認識一下其他烹飪雞蛋的方法,看看如何花式點雞蛋。水煮蛋 Boiled egg# Soft boiled egg 半熟水煮蛋(蛋黃溏心)↓# hard boiled egg 全熟水煮蛋↓有的時候會直接寫成hard-cooked
  • 4種雞蛋做法的英文表達Get起來~
    蒸蛋(Chinese)steamed egg  * 嚴謹來說,前面「Chinese」一詞其實可加可不加,畢竟全世界都吃雞蛋,只是「蒸」蛋的做法還是咱們國人比較偏愛,尤其是老一輩的人,像「雞蛋羹」試問誰小時候沒有吃過!雖然看似是簡簡單單的「蒸蛋」,但是對於火候的時間的把握,也是蠻鍛鍊新手的。如果外國人聽不懂的話,可以帶上,順百告訴他這是中國特色!
  • 口語:「雞蛋單面煎」用英語怎麼表達?
    口語:「雞蛋單面煎」用英語怎麼表達?如果我們只是為了記英語單詞,我們只需「用」中文:「用」中文記住英語sunny-side-up即「雞蛋單面煎」。如果我們是為了「學」英語,「用」英語(把英語用起來),「說」英語,我們就需要「用」學過或沒學過的「英語」:fry/cook chicken egg,one side,the yolk(蛋黃),the whites(蛋清蛋白)等等,然後說出並用下面英語理解和記住sunny-side-up(見到它就能說出/「用」下面的英語):
  • LeetCode面試系列 第6天:No.9 - 迴文數
    Leetcode今天要給大家分析的面試題是 LeetCode 上第 9 號問題,LeetCode - 9. 迴文數https://leetcode-cn.com/problems/palindrome-number/題目描述判斷一個整數是否是迴文數。