動態規划——樓層扔雞蛋問題


前言

大一的時候藍橋杯省賽遇到過(作為非編程題的壓軸題),這次看的別人的面經也多次出現,就寫篇博文總結一下。

題目

有一棟樓共100層,一個雞蛋從第N層及以上的樓層落下來會摔破, 在第N層以下的樓層落下不會摔破。給你2個雞蛋,設計方案找出N,並且保證在最壞情況下,最小化雞蛋下落的次數。

解析

無腦二分法(最多人想到的偽解法)

當時省賽沒注意審題,就想的這種方法,首先需要確定的是,在最壞的情況下,求最小化嘗試次數,所以肯定不是無腦二分那么簡單了。
例如,你第一次扔第50層,碎了如果你再選擇二分,直接到25層又碎的話,兩個雞蛋就都沒了,接下來你咋試啊?
所以你接下來只能從1層到49層一個一個試了,最終嘗試次數為50次。

假設法

首先,假設答案,也就是最小嘗試次數為x,此時從第x層開始扔,有兩種情況:

  • 碎了,那么只能從1到x-1一個一個試了,加上前面扔的一次,總結果為x次,符合,這也是為什么選擇第x層的原因,如果選擇其他層,又碎了的話,則最小嘗試次數肯定不等於x,這就與假設相悖了。
  • 沒碎,那么直接把第1到x層拋棄掉,當作不存在(因為雞蛋不會在這范圍內碎掉),我們把第x+1層當成第1層,嘗試次數為x-1(因為剛剛扔了1次,最小嘗試次數減1),此時就從第x-1層(真實層數為x+x-1)開始扔,同樣又會出現兩種情況:
  • 第二次碎了,則是從第1層到第x-2層開始扔 ,總嘗試次數同樣是x
  • 第二次沒碎,還是之前原理,這次從x-2層開始,以此類推,一直扔到最后一層或碎了為止。

最終結果就是\(x+(x-1)+(x-2)...+1 = 100\),解得\(x = 14\)

題目升級版本

樓層M,雞蛋數N,求最壞情況下的最小次數。

動態規划法

理解了上面的假設法,再學過動態規划的話,這里應該就問題不大了。
狀態轉移方程如下:

\[f[m][n] = min(f[m][n],1+max(f[k-1][n-1],f[m-k][n]),k\in[1,m-1]) \]

解釋:當有n個雞蛋時,所需嘗試的樓層數為m,此時將雞蛋扔在第k層,則有兩種情況

  • 碎了,那么接下來只需要嘗試1到k-1層,雞蛋數為n-1,此時問題不就轉化成了樓層數k-1,雞蛋數n-1,求最壞情況下的最小次數嗎?
  • 沒碎,那么直接把第1到k層拋棄掉,只需要嘗試第k+1到m層,雞蛋沒碎,所以扔為n,此時問題不就轉化成了樓層數m-k,雞蛋數n,求最壞情況下的最小次數嗎?
  • 為什么取MAX?因為是最壞的情況,所以取碎了與沒碎中的最大情況。

代碼如下:

int superEggDrop(int egg,int floor){
	int ans[floor+1][egg+1];
	for(int m = 1;m <= floor;m++)
		for(int n = 1;n <= egg;n++)
			ans[m][n] = m;//最壞的情況下,自然是所有樓層試一遍,同時這也是雞蛋數為1時的答案

	for(int m = 1;m <= floor;m++)
		for(int n = 2;n <= egg;n++)//n必須從2開始,如果是1,就會出現ans[k-1][1-1=0],顯然不存在0雞蛋的情況
			for(int k = 1;k <= m-1;k++)
				ans[m][n] = min(ans[m][n],1+max(ans[k-1][n-1],ans[m-k][n]));
	return ans[floor][egg];
}

然而,該解法的時間復雜度為\(O(km^2)\),空間復雜度為\(O(mn)\),顯然還可以繼續優化。

動態規划+二分優化

對於\(f[k-1][n-1]\)\(f[m-k][n]\),當在第三重循環中,\(m,n\)不變,我們可以將其當作系數,只有\(k\)\([1,m-1]\)的范圍內一直增加,而\(k\)又與樓層數有關,顯然,當樓層數增加時,測試次數一定增加(100層和101層,顯然100更有利吧?)。
\(f[k-1][n-1]\)\(f[m-k][n]\),前者\(k\)系數為正,后者\(k\)系數為負,一個遞增,一個遞減,我們就可以找二分它們的交點,使得無論碎不碎,它們的測試結果都相同,使得時間復雜度為\(O(kmlogm)\)

int superEggDrop(int egg,int floor){
	int ans[floor+1][egg+1];//雞蛋數只需要考慮兩種情況
	for(int m = 1;m <= floor;m++)
		for(int n = 1;n <= egg;n++)
			ans[m][n] = m;//最壞的情況下,自然是所有樓層試一遍,同時這也是雞蛋數為1時的答案

	for(int m = 2;m <= floor;m++)//當樓層數為1時,結果必然是1
		for(int n = 2;n <= egg;n++){//n必須從2開始,如果是1,就會出現ans[k-1][1-1=0],顯然不存在0雞蛋的情況
			int l = 1,r = m;//范圍是[l,r)
			while(l+1 < r){
				int k = (l+r)/2;
				int l_value = ans[k-1][n-1];
				int r_value = ans[m-k][n];
				if (l_value == r_value){
					l = k;
					break;
				}
				else if (l_value > r_value) r = k;
				else l = k+1;
			}
			ans[m][n] = min(ans[m][n],1+max(ans[l-1][n-1],ans[m-l][n]));
		}
	return ans[floor][egg];
}

當然了,現在還不是最優解,由於時間問題,這里就不再贅述,有興趣的可以自行百度。


免責聲明!

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



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