动态规划:鸡蛋掉落

问题出处:中文版 LeetCode 第 887 题 - 鸡蛋掉落 问题

问题描述:(源自 LeetCode)

你将获得 K 个鸡蛋,并可以使用一栋从 1 到 N  共有 N 层楼的建筑。

每个蛋的功能都是一样的,如果一个蛋碎了,你就不能再把它掉下去。

你知道存在楼层 F ,满足 0 <= F <= N 任何从高于 F 的楼层落下的鸡蛋都会碎,从 F 楼层或比它低的楼层落下的鸡蛋都不会破。

每次移动,你可以取一个鸡蛋(如果你有完整的鸡蛋)并把它从任一楼层 X 扔下(满足 1 <= X <= N)。

你的目标是确切地知道 F 的值是多少。

无论 F 的初始值如何,你确定 F 的值的最小移动次数是多少?

问题理解:

给定两个参数 K个鸡蛋 N层楼,目标是在任何F值情况下,寻找 最小移动次数

由于F值可以为任意值,则 最小移动次数 的下限应该取 最坏情况 下的值。

因此,问题的核心是寻找一种在 最坏情况 下的 最优方案

由于鸡蛋的数量在寻找过程中是变化的,即某一步骤的鸡蛋数量,取决于前一步骤的鸡蛋是否破碎。

因此,尝试使用 动态规划 的方法来解决问题。

动态规划:

首先,要确定每一步之间的关系表达式,设 最小移动次数 为 \small y,关系表达式为 \small f(N,K),即 \small y = f(N, K)

然后,假设第一步在 X 层掉落鸡蛋,则存在两种情况:

(1)鸡蛋破碎:剩余 K - 1 个鸡蛋,剩余需要确认的楼层数为 X - 1 层;

(2)鸡蛋完好:剩余 K 个鸡蛋,剩余需要确认的楼层数为 N - X 层;

此时,进入第二步尝试,问题已被拆分为两个子问题:

(1)给定 K - 1 个鸡蛋 和 X - 1 层楼,确定 最小移动次数,即 \small f(X-1, K-1)

(2)给定 K 个鸡蛋 和 N - X 层楼,确定 最小移动次数,即 \small f(N - X, K)

由于要找 最坏情况 下的解,因此,对于两个子问题的解,应该取较大的一个解,即

\small max(f(X - 1, K - 1), f(N - X, K))

由于要找 最坏情况 下的 最优解,因此,对于 1 <= X <= N 的所有解,应该取最小的一个解,即

\small y_{1} = max(f(X - 1, K - 1), f(N - X, K)),X = 1

\small y_{2} = max(f(X - 1, K - 1), f(N - X, K)),X = 2

... ...

\small y_{N} = max(f(X - 1, K - 1), f(N - X, K)),X = N

\small y=min(y_{1} ,y_{2} ,...,y_{N} )

当寻找到上一步(子问题)的最优解时,还需要将本次移动次数考虑进去,因此,最终关系表达式为

\small y=min(y_{1} ,y_{2} ,...,y_{N} ) + 1

从而,得到了该问题每一步之间的关系表达式,结合二维数组,最终解决方案代码如下:

class Solution {

  public int superEggDrop(int K, int N) {
    
    // 可直接得知答案
    if (K == 1 || N == 1 || N == 2) {
      return N;
    }

    // 创建二维数组
    int[][] tempArray = new int[K + 1][N + 1];
    // 初始化首行
    for (int colN = 1; colN <= N; colN++) {
      tempArray[1][colN] = colN;
    }
    // 初始化首列和第二列
    for (int rowK = 1; rowK <= K; rowK++) {
      tempArray[rowK][1] = 1;
      tempArray[rowK][2] = 2;
    }

    // 从第(2,3)位置开始遍历,计算每一步的最优解。
    for (int rowK = 2; rowK <= K; rowK++) {
      for (int colN = 3; colN <= N; colN++) {
        int minValue = Integer.MAX_VALUE;
        for (int x = 1; x <= colN; x++) {
          int valueAtX = Math.max(tempArray[rowK - 1][x - 1], tempArray[rowK][colN - x]);
          if (valueAtX < minValue) {
            minValue = valueAtX;
          }
        }
        tempArray[rowK][colN] = minValue + 1;  // 当前步骤最优解 = 上一步最优解 + 当前1步
      }
    }

    return tempArray[K][N];
  }
}

进阶优化:

通过上述方案,可以找到题目的答案,但是,无法通过 LeetCode 检测,因为耗时超过限制。

观察上述代码,可以发现,主要耗时在三次遍历循环部分,耗时将以指数型增长。

通过关系表达式,也可以发现,其中存在着最大值、最小值等连锁判断,复杂程度可想而知。

当一条路走不通,就需要考虑换一条路来试试。

在上述解决方案中,给定 N 和 K 值,设 最小移动次数 为 \small y,并试图寻找 \small y = f(N, K) 的关系表达式。

若调换一下位置,假设 给定 K 和 \small y 值,能否通过寻找 \small N = f(K,y) 的关系表达式,从而得到问题的解?

按照上述转换,问题被转化为 给定鸡蛋数量K 和 可移动次数\small y,可以检测的 最大楼层数\small N_{max} 为多少?

假设在第 \small y 步,从某层(假设为\small m)掉落鸡蛋,则存在两种情况:

(1)鸡蛋破碎:剩余 K - 1 个鸡蛋,剩余可移动步数 \small y-1 次;

(2)鸡蛋完好:剩余 K 个鸡蛋,剩余可移动步数 \small y-1 次;

此时,第\small y-1步的可检测最大楼层数,决定了第 \small y 步的可检测最大楼层数的范围:

(1)鸡蛋破碎时,需要利用 剩余鸡蛋 和 剩余步数 在 \small m下面 的楼层检测出F值。

因此,下面的楼层数最大不能超过\small N_{down} = f(K-1,y-1)

(2)鸡蛋完好时,需要利用 剩余鸡蛋 和 剩余步数 在 \small m上面 的楼层检测出F值。

因此,上面的楼层数最大不能超过\small N_{up} = f(K,y-1)

由此可知,第 \small y 步可检测出的最大楼层数 \small N_{max} = f(K, y) = f(K-1,y-1)+f(K,y-1)+1

使 \small N_{max} \geqslant N 的最小 \small y 值,便是题解。

通过动态规划方法,结合二维数组表示每一步的解,行为步数 \small y 值,列为鸡蛋数 K 值;

根据关系表达式可知,每一个坐标的值等于 上一行同列值 + 上一行前一列值 + 1 (本层)。

由于 \small y 为所求值,创建二维数组时,无法确定 \small y 值,因此,以 一维数组 代替 二维数组 进行计算;

则每一个坐标的值等于 当前位置值 + 前一位置值 + 1(本层)。

至此,已完成进阶解决方案,代码如下:

class Solution {

  public int superEggDrop(int K, int N) {
    
    // 可直接给出解
    if (K == 1 || N == 1 || N == 2) {
      return N;
    }

    // 初始化首行(第一步)
    int[] tempArray = new int[K + 1];
    for (int index = 1; index <= K; index++) {
      tempArray[index] = 1;
    }

    // 从第二步开始计算
    int x = 2;
    while (true) {
      for (int k = K; k > 1; k--) {  // 从后向前计算,以免 k-1 位置过早被新值覆盖,导致后续计算错误。
        tempArray[k] = tempArray[k] + tempArray[k - 1] + 1;
      }
      // 判断是否满足条件
      if (tempArray[K] >= N) {
        return x;
      } else {
        tempArray[1] = x;  // 覆盖首位值
        x++;  // 进入下一步
      }
    }
  }
}

相关文章

《全排列(Java)》

《位运算:减法与补码》

《异或(^)的性质与应用》

《图解:常用排序算法(冒泡、选择、插入、希尔、快速、归并、堆)》

《回溯算法(试探算法)》

《动态规划:单词拆分》

《状态机:只出现一次的数字II》

《最小堆:TopK问题》

《链表:快慢指针》

  • 8
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值