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