9.动态规划:高楼扔鸡蛋
动态规划问题:高楼扔鸡蛋
力扣 887 题 难度:困难
思路分析:
题目是这样:
你面前有一栋从 1 到N
共N
层的楼,然后给你K
个鸡蛋(K
至少为 1)。现在确定这栋楼存在楼层0 <= F <= N
,在这层楼将鸡蛋扔下去,鸡蛋恰好没摔碎(高于F
的楼层都会碎,低于F
的楼层都不会碎)。现在问你,最坏情况下,你至少要扔几次鸡蛋,才能确定这个楼层F
呢?
比方说现在先不管鸡蛋个数的限制,有 7 层楼,你怎么去找鸡蛋恰好摔碎的那层楼?
最原始的方式就是线性扫描:我先在 1 楼扔一下,没碎,我再去 2 楼扔一下,没碎,我再去 3 楼……
以这种策略,最坏情况应该就是我试到第 7 层鸡蛋也没碎(F = 7
),也就是我扔了 7 次鸡蛋。
最好的策略就是二分搜索了:假设七层楼
我先去(1+7)/2=4层扔一下,
如果碎了说明要求的F小于4,我就再去(1+3)/2=2层扔一下。
如果没碎说明要求的F大于4,我就再去(5+7)/2=6层扔一下。
以这种策略,最坏的情况下试到第七层鸡蛋还没碎的话(F=7),或者试到第一层鸡蛋还没碎的话(F=0),无论哪种情况,只需要试log7向上取整等于3次,比刚才尝试其次要少,这就是至少要扔几次,这就是所谓的至少扔几次。
实际上,不限制鸡蛋个数的话二分还是可以的,但是此时鸡蛋有个数限制,二分思路就不行了。
动态规划思路分析:
对于动态规划问题,直接套以前多次提到的框架即可:这个问题有什么“状态”,有什么“选择”,然后穷举:
“状态”就是会发生变化的量,很明显有两个,一个是当前拥有的鸡蛋数K,和需要测试的楼层数N。随着N测试的进行,鸡蛋的个数减少,楼层的搜索范围减少,这就是状态的变化。
"选择"其实就是去选择哪层楼扔鸡蛋。回顾刚才的线性扫描和二分扫描思路,二分搜索每次选择到的楼层区间的中间扔鸡蛋,而线性扫描选择一层层向上测试。不同的选择会有不同的状态转移。
现在明确了状态和选择,动态规划的基本思路就完成了:肯定是一个二维dp数组或者带两个状态参数的dp函数来表示状态转移;外加一个for循环遍历所有的选择,做出最优的选择并且更新状态:
int dp[][]=new int[K][N];
int res;
for (int i = 1; i <=N ; i++) {
res=min(res,这次在第i层楼扔鸡蛋);
}
大致框架就差不多了。
我们选择在第i层楼扔鸡蛋,可能出现两种情况:
1 鸡蛋碎了,那么鸡蛋k的个数减少1,搜索区间向下(1...N)变为(1...i-1)共i-1层楼。
2 鸡蛋没碎,鸡蛋K的个数不变,搜索区间向上(1...N)变为(i+1...N)共计N-i层楼。
如图:
因为要求的最坏情况下扔鸡蛋的次数,所以鸡蛋在第i层楼碎没碎,取决于哪种情况的结果更大:
int dp[][]=new int[K][N];
int res;
for (int i = 1; i <=N ; i++) {
res=min(res,
max(
dp[K-1,i-1],//碎了,向下搜索,楼层数变为i-1
dp[K,N-i] //没碎,搜索区间向上搜索楼层数N-i
));
base case
base case 很容易理解:当楼层数N等于0时,显然不需要扔鸡蛋;
当鸡蛋熟K为1时候,只能线性扫描所有楼层.
int[][] dp = new int[k+1][n+1];//k个鸡对应n层楼
for (int i = 1; i <=n ; i++) { //一个鸡蛋,线性搜索
dp[1][i]=i;
}
for (int eggs = 1; eggs < k + 1; eggs++)
{
dp[eggs][1] = 1; // 1层楼,多个鸡蛋,只需要扔1次
}
完整代码如下:
public int superEggDrop(int k, int n) {
int[][] dp = new int[k+1][n+1];//k个鸡对应n层楼
for (int i = 1; i <=n ; i++) {
dp[1][i]=i;
}
for (int eggs = 1; eggs < k + 1; eggs++)
{
dp[eggs][1] = 1; // 1层楼,多个鸡蛋,只需要扔1次
}
int i;
int j;
for (i=2; i <=k ; i++) {
for (j=1; j <=n ; j++) {
int res=Integer.MAX_VALUE;
for (int l = 1; l <=j ; l++) { //择优选择
res=Math.min(res,Math.max(dp[i-1][l-1],dp[i][j-l])+1);
}
dp[i][j]=res;
}
}
return dp[k][n];
}