Google仍鸡蛋[DP]
這道題是說,100層樓,兩個一模一樣的雞蛋,某層之上扔雞蛋就會碎。問要測試多少次才能找出這層樓來。我曾經(jīng)在去年初的這篇文章里面討論過這個問題的解法,因為只想記錄一下思路和討論過程,寫得很簡略。現(xiàn)在,我想重新整理一下這個問題,再稍稍擴展和挖掘一下。希望可以用盡可能清晰易懂的描述,把這個問題的前后說清楚。
現(xiàn)在只有兩個雞蛋,而算法必須是可行的,就是說要能找出這一層來,所以你得假設(shè)你的運氣最差,這就意味著,我求解的是在每種扔雞蛋的策略下都有一個需要扔的次數(shù)的最大值,而現(xiàn)在需要求解的是這些最大值中的最小值的問題。如果我只有一枚雞蛋,這就意味著,我只能從第一層開始老老實實地一層一層往上試,不能越層;而我的運氣非常非常差,于是我這樣試驗的結(jié)果就是一直試驗到最高一層雞蛋依然沒破,試驗的次數(shù)就等于樓層數(shù)N。
動態(tài)規(guī)劃法求解
現(xiàn)在,我有兩枚雞蛋,第一枚雞蛋從哪一層樓開始扔就顯得至關(guān)重要了。如果第一枚雞蛋碎了,那就回到剛說的只有一枚雞蛋的問題了。我相信很多人立即映射到腦子里的詞是“二分法”,也就是說,第一枚雞蛋從N/2開始扔,如果碎了,扔的樓層數(shù)就是N/2(注意取整);如果沒碎,剩下N/2樓層里繼續(xù)用二分法。可以預計,如果沒碎的情況,扔雞蛋的總次數(shù)要小于N/2。也就意味著,還有潛力可挖,如果不用二分法,讓扔第一枚雞蛋的樓層數(shù)為x,它小于N/2;同時如果第一枚雞蛋沒碎,接下去的策略,在運氣最差的情況下仍然讓扔的次數(shù)等于x,就最經(jīng)濟了。
最常規(guī)的解法是動態(tài)規(guī)劃。可以用動態(tài)規(guī)劃法求解的問題必須滿足這樣的條件,分割成的子問題是最優(yōu)解的時候,原問題也是最優(yōu)解。顯然這個問題滿足這一點:假設(shè)扔的總次數(shù)為f(x),在第一次扔碎了的情況下,接下去只能一層一層試驗,最多從第1層到第x-1層需要試驗x-1次,加上扔第一個雞蛋那一次,總的次數(shù)就是(x-1)+1=x;在第一次沒碎的情況下,就相當于一個新的相似子問題:在N-x層中尋找新的扔雞蛋次數(shù)f(N-x),因此總次數(shù)就是f(N-x)+1。那么:
f(x)=max(x, f(N-x)+1)
同時,遞歸求解的出口是:當x=1,f(x)=1。所以,算法大致如下:
// times[i]表示N取值為i的時候,需要扔多少次private static int[] times;public static int dropEgg(int N) {// 初始化數(shù)組times = new int[N + 1];return dp(N);}private static int dp(int N) {// 兩層樓或一層樓就沒有計算的必要了if (1 == N)return 1;else if (2 == N)return 2;for (int x = 2; x < N; x++) {int max = maxTimes(N, x);if (0 == times[N] || times[N] > max)times[N] = max;}return times[N];}// 碎和不碎的次數(shù)最大值private static int maxTimes(int N, int x) {int sum = dp(N - x) + 1;if (x < sum)return sum;elsereturn x;}“兩個雞蛋”到“m個雞蛋”
現(xiàn)在把問題擴展一下,由兩個雞蛋擴展到m個雞蛋,times數(shù)組第一維表示最多可以扔幾次,第二維表示扔第幾次:
public static int dropEgg(int N, int m) {// 初始化數(shù)組times = new int[m + 1][N + 1];return dp(N, m);}private static int dp(int N, int m) {if (1 == m || 1 == N || 2 == N)return N;for (int time = 2; time <= m; time++)for (int x = 2; x < N; x++) {int max = maxTimes(N, x, time);if (0 == times[time][N] || times[time][N] > max)times[time][N] = max;}return times[m][N];}// 碎和不碎的次數(shù)最大值private static int maxTimes(int N, int x, int m) {int sum = dp(N - x, m) + 1;if (x < sum)return sum;elsereturn x;}尋找規(guī)律
還是回到兩個雞蛋,隨著N的值改變,可以發(fā)現(xiàn)x呈現(xiàn)這樣的變化:
N=1, x=1?
N=2, x=2?
N=3, x=2?
N=4, x=3?
N=5, x=3?
N=6, x=3?
N=7, x=4?
N=8, x=4?
N=9, x=4?
N=10, x=4?
N=11, x=5?
N=12, x=5?
N=13, x=5?
N=14, x=5?
N=15, x=5?
N=16, x=6?
N=17, x=6?
N=18, x=6?
N=19, x=6?
N=20, x=6?
N=21, x=6?
……
換言之,x=1的有1項,x=2的有2項,x=3的有3項……網(wǎng)上最多的問法是當N=100的時候,x是多少,根據(jù)這個規(guī)律:
(1+x)*x/2>=100
得知x的最小值是14。
下面來證明一下這個猜想:
因為當扔雞蛋的最小次數(shù)為x的時候,第一次從x層開始扔,如果碎了,那么接下去就要扔x-1次;如果沒碎,接下去就變成了扔雞蛋最小次數(shù)為x-1的子問題了,此時再扔的樓層數(shù)變成了x+(x-1),在這種情況下碎和不碎兩種情況下需要再扔的次數(shù)是相等的,已經(jīng)是最經(jīng)濟的扔法了,繼續(xù)之,可以得到,扔雞蛋最小次數(shù)為x的時候,最高可以檢測到的樓層數(shù)為:
x+(x-1)+(x-2)+……+1
正好是一個等差數(shù)列求和的公式。
這也是為什么,我們可以從網(wǎng)上找到的扔雞蛋問題,好多都是N等于15、21、28、36這樣的數(shù),這正好等于這個等差數(shù)列和。
現(xiàn)在把問題稍稍轉(zhuǎn)變一下,把雞蛋數(shù)量的限制去掉,再把求爬樓梯的限制加上,不妨再來求解:
如果雞蛋數(shù)量無限,但是假如說扔一次雞蛋需要耗費力氣a,每爬一層樓(無論向上還是向下)需要耗費力氣b,現(xiàn)在用怎樣的扔雞蛋策略,可以讓耗費的總力氣量最小?
總結(jié)
以上是生活随笔為你收集整理的Google仍鸡蛋[DP]的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 哈希表冲突解决
- 下一篇: 2012年CS毕业生