@
PS:(藍橋杯摔手機就是根據扔雞蛋過來的)
思路講解
你有兩個雞蛋,在一百層的樓上,盡可能少的嘗試次數可以找出在那一層掉落而不碎
吐個槽先
正如昨天所說,這道題據說它最早見於谷歌的某次面試,由於題目表述容易,而解答相對麻煩,於是被很多人采用,廣泛見於一些算法、規划的面試里。
要說清楚這個問題有點難度,有興趣的小伙伴務必要靜下心來仔細看。
首先我想說,該問題的情境構造是有缺陷的。因為在現實中,影響雞蛋是否會破裂的最重要因素應該是地面的堅硬和平整程度,而不是雞蛋下落的高度:把雞蛋從100層樓頂扔進棉花堆,和從2樓扔向堅硬的水泥地面:依常識判斷,還是后者更容易碎。就像那句話說的,能夠殺死你的是突然的速度改變,而不是高速本身:你會在接觸地面的一瞬間死去,而不是死於高速墜落。
不過你懂的,這是題目,認真你就輸了。
我們只需要遵循題目本身的設定就好,吐槽可以,不改變事實。
一個雞蛋的無助
為了更好地解釋這個問題,讓我們先看一個簡化的情況:如果你手頭只有一個雞蛋,該如何找到這個最高安全樓層?
——顯然只能一層層地試。
簡單地說,這個過程就是先在1樓把雞蛋扔下地,看看碎不碎。
碎了,那么得到最高安全樓層為0。
如果沒碎,那么拾起這個雞蛋,上2樓,再扔下地,看碎不碎。
同樣,碎了,那么得到最高安全樓層為1;沒碎,那就再拾起雞蛋奔3樓……
重復這個過程,直到發生兩個結果之一:雞蛋在第N層上首次碎掉,那么得到最高安全樓層為N-1;或者雞蛋一直沒碎,最高安全樓層為100(其實應該大於100,但這道題默認最大是100,所以姑且認為是100)。
由於雞蛋隨時可能會在某一層突然碎掉,而且一旦碎掉就再也沒有雞蛋可用,所以在整個過程中不能有任何投機取巧,只能逐層嘗試,否則在任何一層上蛋碎了,我們都不能准確得出最高安全樓層。
一個雞蛋的情況大致如此。雖然答案簡單粗暴,但涉及的思路會對兩個雞蛋的情況有所啟示。
兩個雞蛋的鬧騰
兩個雞蛋的情況下,基本的想法也不難理解:
由於有兩個雞蛋可以用,所以第一個雞蛋得用來確定一個比較大的范圍。
但是心太黑了也不合適,步子邁得太大容易扯着蛋
比如你一下子跑到50樓扔雞蛋,看上去確實大量減少了工作量,但是蛋碎了怎么辦呢?如果第一個蛋碎了,這時可以使用的蛋就只剩一個,而剛剛說了,只有一個蛋時我們沒有選擇,只能逐層去試。
這里體現了切分范圍的思想,但同時體現了一個重要事實:
雞蛋碎了比沒碎要糟糕。
正因為這個事實的存在,因此在1-100層樓之前,我們第一個蛋的首次嘗試應該向1這邊(較可能不碎)傾斜,而不是向100那邊(較可能碎)。
這樣一個思路就出爐了:把100分成10個10層,第一個雞蛋用來確認在哪個10層里,第二個雞蛋用來確認具體層數。
具體來說就是,拿着第一個雞蛋從第10層嘗試,只要沒碎就再上10層,直至碎了或者爬到樓頂,這樣確定了十位數的范圍。然后再用第二個雞蛋逐層嘗試。
比如第一個雞蛋:
第10層扔下沒碎,
第20層扔下沒碎,
第30層扔下沒碎,
第40層扔下碎了;
那么就拿第二個雞蛋從31層至39層開始依次逐層嘗試,直至排查出最高安全樓層為止。
這個思路已經基本成型,也比較接近答案了,只需要最后一點調整。
最后的雕琢
上面這個10*10的思路比逐層嘗試已經有顯著改進。
它的基本思路是,將100層切分成兩個維度,由兩個雞蛋分別控制一個維度。
換言之,是將100層切分成若干個區塊,由第一個雞蛋確定最高安全樓層所屬的區塊,再由第二個雞蛋逐層確定其具體的位置。大致思路如下:
每十個樓層,算作一個區塊的話
1-10 —— 區塊1
11-20 —— 區塊2
21-30 —— 區塊3
……
但仔細思考的小伙伴可以發現,這個答案還有問題。
比如如果雞蛋最高安全樓層為16或者96,用剛剛那個思路的話,這兩種情況的總嘗試次數並不一樣:最高安全樓層為16時,第一個雞蛋試了2次就定位了區塊;而最高安全樓層為96時,第一個雞蛋試了10次才定位了區塊。雖然在區塊內部的第二個雞蛋的逐層嘗試是一樣的,但96層對應的總嘗試次數就多得太多了。
原因就是10*10的區塊均勻划分對大數不利。
因為碎和不碎這兩種狀態是不對稱的,所以第一個雞蛋的嘗試的過程只能從小數逐漸嘗試到大數,而不能反着來。所以均勻划分區塊對大數是不公平:同樣在每個區塊里的第6層,第一個雞蛋走到十位數以9開頭的區塊里需要的次數太多了。
明白了這個缺陷,也就知道了改進的基本思想:還是要對100找出一種二維區塊划分,但不是均勻划分。對於比較小的樓層部分,其包含的樓層范圍可以適當多;越向大數部分走,其包含的樓層范圍越來越小。從下往上,每一個區塊內所含樓層遞減。
對於最高安全樓層比較低的情況,第一個雞蛋試的次數少;所以最高安全樓層比較高的情況,則讓第二個雞蛋試的次數少。用第二個雞蛋的嘗試次數的減少來彌補第一個雞蛋需要嘗試的次數的遞增,使兩個雞蛋在不同維度上的嘗試次數達到一種微妙的平衡。
按照這個思路,要把上面那個均勻的區塊切分改進如下:
1,2,3,4……,x ——區塊1
x+1,x+2,…… ——區塊2
……
91,92,93,94 ——區塊n-3
95,96,97, ——區塊n-2
98,99, ——區塊n-1
100, ——區塊n
由於每個區塊逐次遞減1,所以最后的區塊里包含1個樓層;相應地,區塊總數也可能會變化,不再是10個區塊。
那么這里的x和n會是多少呢?
最后一個區塊里包含1個樓層,
倒數第二個包含2個樓層,
倒數第三個包含3個樓層,
繼續,各區塊包含的樓層是4,5,6,……
於是問題轉變為,到包含多少個樓層的時候,100個樓層全部分配完?
由於1+2+3+……+13=91,
而1+2+3+……+14=105,
就是說從1開始累加,加到14時,總和第一次大於100:
所以上圖里的x是14。
答案揭曉
注意,從14加到1的和是105,還多了5,多的部分可在各區塊內分配扣減,不同的扣減方法會對應不同樓層分布的答案。
比如把原來10*10的划分進行如下調整:
10, 10, 10, 10, 10, 10, 10, 10, 10, 10,
14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 1,
這樣對應的答案就是:
先上14樓把第一個雞蛋扔下去。
沒碎,則上(14+13=)27樓再試。
還沒碎,上(27+12=)39樓試。
還沒碎,上(39+11=)50樓試。
還沒碎,上(50+10=)60樓試。
還沒碎,上(60+9=)69樓試。
還沒碎,上(69+8=)77樓試。
還沒碎,上(77+7=)84樓試。
還沒碎,上(84+6=)90樓試。
還沒碎,上(90+5=)95樓試。
還沒碎,上(95+4=)99樓試。
還沒碎,上100樓試。
如果中間任何一次雞蛋碎了,則用第二個雞蛋從前一次嘗試的下一層開始逐層試。如果14樓的第一次嘗試就碎了,那么用第二個雞蛋從1樓開始逐層試。
這時我們來檢查一下,當最高安全樓層為幾個比較討厭的特殊值和最后的邊界值時,分別需要嘗試的次數:
——最高安全樓層為13時,第一個雞蛋第1次就碎了。第二個雞蛋從1逐層試到13要試13次,一共試了1+13=14次。
——最高安全樓層為26時,第一個雞蛋試了14,27,在第2次碎了。第二個雞蛋從15逐層試到26,要試12次,一共是2+12=14次。
——最高安全樓層為38時,第一個雞蛋試了14,27,39,在第3次碎了。第二個雞蛋從28試到38,要試11次,一共是3+11=14次。
……………………
——最高安全樓層為98時,第一個雞蛋試了14,27,39,50,60,69,77,84,90,95,99,在第11次碎了。第二個雞蛋從96試到98試3次,一共是11+3=14次。
——最高安全樓層為99時,第一個雞蛋試了14,27,39,50,60,69,77,84,90,95,99,100,在第12次碎了。第二個雞蛋不用試。一共是12次。
——最高安全樓層為100時,第一個雞蛋試了14,27,39,50,60,69,77,84,90,95,99,100,最后也沒碎,第二個雞蛋還是不用試。一共也是12次。
最后的兩個情況是邊界的特殊情況,之前的各次嘗試都已經是最糟糕的情況,其他的所有情況總嘗試次數都會小於14:
比如最高安全樓層是24,第一個雞蛋試了14,27,在第2次碎了。第二個雞蛋從15到25試11次,一共是2+11=13次。
再比如最高安全樓層為71時,第一個雞蛋試了14,27,39,50,60,69,77,在第7次碎了。第二個雞蛋從70到72試3次,一共是7+3=10次。
綜上,問題解決:第一個雞蛋依次試14,27,39,50,60,69,77,84,90,95,99,100。中間任何一次破碎了,就從上一次的下一層開始用第二個雞蛋逐層嘗試,直至第二個雞蛋也破碎為止。
用這個方法,總次數一定不超過14次:當最高安全樓層越來越高時,第一個雞蛋試的次數越來越多,但第二個雞蛋試的次數越來越少,兩者始終維持着一種平衡。
余 音
1、14到底怎么算出來的?
——從x遞減到1的數列要累加超過100,所以14其實是滿足x(x+1)/2>100的最小正整數。
更進一步說,如果總樓高不是100而是n,則解不等式x(x+1)/2>n得到的最小正整數x就是用第一個雞蛋首次嘗試所邁開的“步長”,也是使用正確方法所得到的最少嘗試次數。
2、本題解答唯一嗎?
——如前所述,14這個最少嘗試次數是滿足x(x+1)/2>100的最小正整數,這是唯一的。但14遞減到1的數列累加為105,比100多出的部分可在各個區域任意分配,這樣對應具體的樓層分布方案會有所不同,但都可行。比如用第一個雞蛋依次嘗試13,25,36,46,55,64,72,79,85,90,94,97,99,100就是另一個正確解答,其最少嘗試次數也是14。
3、思考的難點在哪里?
——難點在於想到用兩個雞蛋分別控制兩個維度,並且兩個維度之間要保持微妙的平衡,剩下的思考過程就比較自然了。但是涉及到與具體的數打交道時還是要細心,尤其是在處理邊界情況時要格外小心。
LeetCode 887. 雞蛋掉落
你將獲得 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 <= 100
1 <= N <= 10000
PS:
* 雞蛋掉落,谷歌面試題,
* 有 K 個雞蛋,有 N 層樓,用最少的操作次數 F 檢查出雞蛋的質量。
*
* 思路:
* 本題應該逆向思維,若你有 K 個雞蛋,你最多操作 F 次,求 N 最大值。
*
* dp[k][f] = dp[k][f-1] + dp[k-1][f-1] + 1;//這里就當成當前這一層所能決定的層數為雞蛋沒摔的層數+雞蛋摔了的層數+當前這一層
* 解釋:
* 0.dp[k][f]:如果你還剩 k 個蛋,且只能操作 f 次了,所能確定的樓層。
* 1.dp[k][f-1]:蛋沒碎,因此該部分決定了所操作樓層的上面所能容納的樓層最大值
* 2.dp[k-1][f-1]:蛋碎了,因此該部分決定了所操作樓層的下面所能容納的樓層最大值
* 又因為第 f 次操作結果只和第 f-1 次操作結果相關,因此可以只用一維數組。
class Solution {
//二維數組的解法
// public int superEggDrop(int K, int N) {
// if (N == 1) {
// return 1;
// }
// int[][] f = new int[N + 1][K + 1];
// for (int i = 1; i <= K; ++i) {
// f[1][i] = 1;
// }
// int ans = -1;
// for (int i = 2; i <= N; ++i) {
// for (int j = 1; j <= K; ++j) {
// f[i][j] = 1 + f[i - 1][j - 1] + f[i - 1][j];
// }
// if (f[i][K] >= N) {
// ans = i;
// break;
// }
// }
// return ans;
// }
//一維數組的解法
public int superEggDrop(int K, int N) {
int[] dp = new int[K + 1];
int ans = 0; // 操作的次數
while (dp[K] < N){
for (int i = K; i > 0; i--) // 從后往前計算
dp[i] = dp[i] + dp[i-1] + 1;
ans++;
}
return ans;
}
}