昨天面试的时候面试到的,当时想不出来,太贪了,一直往贪心的方向想,T_T。
今天来写一篇总结一下,不然下次遇到又不会就很尴尬了。
问题描述:
有两个鸡蛋,100层楼,鸡蛋可以再任意一层扔下去碎掉,也可以不碎,不碎的会可以继续拿起来用,问你至少需要多少次才能知道碎的层数。可能第一层碎了,就是所有层数都会碎,有可能最后一层没有碎,即所有层都不会碎。
样例:
2个鸡蛋3层楼的次数是2,2个鸡蛋100层楼的次数是14。
思路分析:
一开始我是想到贪心,可能因为前面的题基本都是二分,所以思路转不过来了。比如先从50层扔,不碎,就从75层开始,碎了,只能从第一层开始,这种做法至少需要50次。不符合要求。之后就说了贪心的思路,发现都不行,不是最贪的,其实贪心是可以的,不过并不是普遍的解,所以就算我真的推出贪心的思路也是没用的。
之后我想到会不会在某个极点的时候存在最优值,就想着去推公式(想起打ACM的时候都是这样,一看,不会,暴力dfs求解,推公式,就想着推下公式,发现时间根本就没有T_T,写完4,5个样例面试官基本就回来问你会了没有了。推不出来,心底凉凉的。)
既然这样都不行,就想想DP思路,最后的时候才想到是不是动态规划,看来是没救了。怎么动态规划呢,动态规划的核心的子问题的重叠,以及可以分成几个状态分别求解。怎么分,可以继续用上面那个贪心的思路换一下,如果我从50层开始扔,会出现什么情况呢?
碎了:那么只好老老实实的从第一层开始慢慢扔,这个状态可以看成是你剩下1个鸡蛋,却有49层,也就是m-1个鸡蛋,50-1层楼。(m,n分别表示原有的鸡蛋数和原来的层数)那么就可以想出第一种状态,dp[m][n] = dp[m-1][50-1]。
不碎:很幸运,没有碎,谢天谢地。那么就可以继续二分扔,但是无论你怎么扔,你都会到这样的一个状态,这个状态可以看成你现在有m个鸡蛋,但是层数只剩下50层,也就是m个鸡蛋,n-50层楼,这样就是第二种状态,也就是dp[m][n] = dp[m][n-50]
然后就没有了,这么想的会是不是就很简单?(当初真的想不到,一直在想怎么保证贪心的前提下,代价不变,凉凉)
总结一下,就是你有两颗鸡蛋,你可以从一个状态出发,之后你一定会面临两种结果,要么碎,要么不碎,这样是不是就回到了动态规划的基本思路,一般有选择的问题基本上都是动态规划问题了。(总不能写个暴力2^n方是吧)
递推公式:
dp[i][j] = min(dp[i][j],1 + max(dp[i-1][k-1],dp[i][j-k]))
解释一下:
dp[i-1][k-1]:表示在第k层扔碎了的情况
dp[i][j-k]:表示第k层扔了不碎的情况
k:范围在1~j之间
代码:
/*参数说明:
* n:表示层数
* m:表示鸡蛋数
* 返回值说明:
* 返回一个整数,表示至少的次数。
*/
int computeMinDropsInWorstCase(int m,int n)
{
int** dp = new int*[m+1];
for(int i=0;i<=m;i++) dp[i] = new int[n+1];
for(int i=0;i<=m;i++)
{
dp[i][1] = 1;
dp[i][0] = 0;
}
for(int i=0;i<=n;i++)
dp[1][i] = i;
for(int i=2;i<=m;i++)
{
for(int j=2;j<=n;j++)
{
dp[i][j] = 1000000;
for(int k=1;k<=j;k++)
dp[i][j] = min(dp[i][j],1 + max(dp[i-1][k-1],dp[i][j-k]));
}
}
int Number = dp[m][n];
for(int i=0;i<=m;i++) delete[] dp[i];
delete[] dp;
return Number;
}
参考链接:
https://www.cnblogs.com/ltang/archive/2010/11/23/1885791.html
https://blog.youkuaiyun.com/lengxiao1993/article/details/52551492