887. 鸡蛋掉落

力扣地址

动态规划+二分查找

​ 首先我们根据dp(K, N)数组的定义(有K个鸡蛋面对N层楼,最少需要扔 dp(K, N) 次),很容易知道K固定时,这个函数随着N的增加一定是单调递增的,无论你策略多聪明,楼层增加的话,测试次数一定要增加。

​ 注意dp(K - 1, i - 1)dp(K, N - i)这两个函数,其中i是从 1 到N单增的,如果我们固定KN把这两个函数看做关于i的函数,前者随着i的增加应该也是单调递增的,而后者随着i的增加应该是单调递减的

image.png

求二者的较大值,再求这些最大值之中的最小值,其实就是求这两条直线交点,也就是红色折线的最低点嘛。这个时候就可以用二分搜索来找这个最低点

回顾这两个dp函数的曲线,我们要找的最低点其实就是这种情况:

for (int i = 1; i <= N; i++) {
    if (dp(K - 1, i - 1) == dp(K, N - i))
        return dp(K, N - i);
}

即:

lo, hi = 1, N
        while lo <= hi:
            mid = (lo + hi) // 2
            broken = dp(K - 1, mid - 1) # 碎
            not_broken = dp(K, N - mid) # 没碎
            # res = min(max(碎,没碎) + 1)
            if broken > not_broken:
                hi = mid - 1
                res = min(res, broken + 1)
            else:
                lo = mid + 1
                res = min(res, not_broken + 1)

        memo[(K, N)] = res
        return res

如果用dp数组来表示上文dp函数的定义即是:

dp[k][n] = m
# 当前状态为 k 个鸡蛋,面对 n 层楼
# 这个状态下最少的扔鸡蛋次数为 m

确定当前的鸡蛋个数和面对的楼层数,就知道最小扔鸡蛋次数。最终我们想要的答案就是dp(K, N)的结果。

  1. 重新定义dp数组的定义,确定当前的鸡蛋个数和最多允许的扔鸡蛋次数,就知道能够确定F的最高楼层数
dp[k][m] = n
# 当前有 k 个鸡蛋,可以尝试扔 m 次鸡蛋
# 这个状态下,最坏情况下最多能确切测试一栋 n 层的楼

# 比如说 dp[1][7] = 7 表示:
# 现在有 1 个鸡蛋,允许你扔 7 次;
# 这个状态下最多给你 7 层楼,
# 使得你可以确定楼层 F 使得鸡蛋恰好摔不碎
# (一层一层线性探查嘛)

这里m是一个次数上界,下面我们就是通过将dp[k][m]逼近楼层数,来找到这个m,

题目不是给你K鸡蛋,N层楼,让你求最坏情况下最少的测试次数m 吗?while循环结束的条件是dp[K][m] == N,也就是给你K个鸡蛋,允许测试m次,最坏情况下最多能测试N层楼

  1. 状态转移

image.png

1、无论你在哪层楼扔鸡蛋,鸡蛋只可能摔碎或者没摔碎,碎了的话就测楼下,没碎的话就测楼上

2、无论你上楼还是下楼,总的楼层数 = 楼上的楼层数 + 楼下的楼层数 + 1(当前这层楼)

根据这个特点,可以写出下面的状态转移方程:

dp[k][m] = dp[k][m-1] + dp[k-1][m-1] + 1

dp[k][m - 1]就是楼上的楼层数,因为鸡蛋个数k不变,也就是鸡蛋没碎,扔鸡蛋次数m减一;

dp[k - 1][m - 1]就是楼下的楼层数,因为鸡蛋个数k减一,也就是鸡蛋碎了,同时扔鸡蛋次数m减一。

PS:这个m为什么要减一而不是加一?之前定义得很清楚,这个m是一个允许的次数上界,而不是扔了几次。

即:

int superEggDrop(int K, int N) {
    // m 最多不会超过 N 次(线性扫描)
    int[][] dp = new int[K + 1][N + 1];
    // base case:
    // dp[0][..] = 0
    // dp[..][0] = 0
    // Java 默认初始化数组都为 0
    int m = 0;
    while (dp[K][m] < N) {
        m++;
        for (int k = 1; k <= K; k++)
            dp[k][m] = dp[k][m - 1] + dp[k - 1][m - 1] + 1;
    }
    return m;
}

代码

class Solution {
    public int superEggDrop(int k, int n) {
        int[][] dp=new int[k+1][n+1];
        int m=0;
        while(dp[k][m]<n){
            m++;
            for(int i=1;i<=k;i++){
                //楼层数=上层+下层+本层
                dp[i][m]=dp[i][m-1]+dp[i-1][m-1]+1;
            }
        }
        return m;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值