动态规划问题:高楼扔鸡蛋

力扣 887 题 难度:困难

9.动态规划:高楼扔鸡蛋_线性扫描

思路分析:

题目是这样:
你面前有一栋从 1 到NN层的楼,然后给你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层楼。
如图:

9.动态规划:高楼扔鸡蛋_动态规划_02
因为要求的最坏情况下扔鸡蛋的次数,所以鸡蛋在第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];  
}