动态规划——扔鸡蛋问题的递归算法与非递归算法

本文探讨了扔鸡蛋问题的动态规划解决方案,包括基础版与进阶版问题的递归和非递归算法。通过分析鸡蛋数量、楼层高度与投掷次数之间的关系,提出了最优方法以最小化最坏情况下的投掷次数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

动态规划——扔鸡蛋问题的递归算法与非递归算法

基础版
有一幢高100层的楼,鸡蛋从xxx层投下时刚好会碎。现持有2个完全相同的鸡蛋,试设计一个最优方法来找出xxx,使以此方法投下鸡蛋时,最坏情况下所投掷的总次数N(2,100)N(2,100)N(2,100)最少。

进阶版
有一幢高fff层的楼,鸡蛋从xxx层投下时刚好会碎。现持有eee个完全相同的鸡蛋,试设计一个最优方法来找出xxx,使以此方法投下鸡蛋时,最坏情况下所投掷的总次数N(e,f)N(e,f)N(e,f)最少。

eee为鸡蛋数,fff为楼层高度,N(e,f)N(e,f)N(e,f)为所求最优算法的投掷总次数,φ\varphiφ为某一算法,ψ\psiψ为全量算法集合,xxx为鸡蛋破碎楼层,ne,f(x∣φ)n_{e,f}(x|\varphi)ne,f(xφ)为确定(e,f,φ,x)(e,f,\varphi,x)(e,f,φ,x)情况下所需投掷的次数。
我们可以得出N(e,f)N(e,f)N(e,f)的表达式如下
N(e,f)=min⁡φ∈ψmax⁡0≤x≤fne,f(x∣φ)N(e,f)=\min\limits_{\varphi \in \psi}\max\limits_{0\leq x\leq f}n_{e,f}(x|\varphi)N(e,f)=φψmin0xfmaxne,f(xφ)
带着表达式,我们来思考这个问题。

相信凡是有过编程经验的人,都听说过二分法。
介于<,≤,=,≠,≥,><,\leq ,=,\neq ,\geq ,><,,=,=,,>均是二元关系运算符,二分法成为了最高效的查找算法。其身影不仅出现在查找过程中,在二叉树、归并排序等等算法中均得以一窥。
然而本题并非二分法的领域,以基础版问题为例,若以二分法投鸡蛋,则首次在第50层投掷(记为T1=50T_1=50T1=50)后,若鸡蛋破碎,即0<x≤500<x\leq 500<x50时,第二次必须从第1层开始投掷,否则若从第2层开始投掷且鸡蛋破碎,则无法确定x=1x=1x=1还是x=2x=2x=2。若首次在第50层投掷,最坏情况即x=50x=50x=50,此时投掷次数N=50N=50N=50,这显然并非最优方法。
但是二分法仍然可以为我们带来启示。根据上述推断,我们可以得出两条规律:

  • N(e,f)≥⌊log2f⌋where e≥⌊log2f⌋N(e,f)\geq \lfloor log_2 f\rfloor\quad{\rm where}\ e\geq \lfloor log_2 f\rfloorN(e,f)log2fwhere elog2f
  • N(1,f)=fN(1,f)=fN(1,f)=f

第一条为二分法所规定的投掷次数下限,即鸡蛋数量充足时至少要投掷的次数,不过事实上这一条的参考意义有限。
第二条为仅有一个鸡蛋时的投掷次数,这一条则是重要的边界条件之一。
依然由T1=50T_1=50T1=50,我们考虑T1=49T_1=49T1=49,最坏情况仍是x=49x=49x=49,此时的有N=49<50N=49<50N=49<50。可以发现T1T_1T1减少时,NNN同步在减少。那是否T1T_1T1越小越好呢?并非如此。
我们在T1T_1T1从50减少到49的过程中,实际默认了“对x>T1x>T_1x>T1的情况下,使用2个鸡蛋总能在N−1N-1N1次内找出xxx”。随着T1T_1T1越来越小,这一条件的实现越来越困难。但好在对于x≤T1x\leq T_1xT1的情况,我们容易确认最坏情况下需要N=T1N=T_1N=T1次来找出xxx。那么当首次投掷鸡蛋不破碎时,我们问题变更为在T1+1T_1+1T1+1至100间找出T2T_2T2。从而T1+1T_1+1T1+1至100间至多投掷N−1N-1N1次。由T1=NT_1=NT1=N,容易得出T2=N−1T_2=N-1T2=N1。从而有
T1+T2+…+TN=N+N−1+…+1=N(N−1)/2≥100T_1+T_2+…+T_N=N+N-1+…+1=N(N-1)/2\geq 100T1+T2++TN=N+N1++1=N(N1)/2100
从而N≥14N\geq 14N14,即最优时N=14N=14N=14。此时的投掷方法为:

  • 按14,27,39,50,60,69,77,84,90,95,99,100(103)的次序投掷,任一次破碎时,从上一个未破碎节点开始逐个投掷。

\frac{}{}

当问题升级为进阶版时,就不能简单地用T1=NT_1=NT1=N来判断了,但我们仍然可以使用基础版的思想。
以下事实显然:

  • N(1,f)=fN(1,f)=fN(1,f)=f
  • N(e,0)=0N(e,0)=0N(e,0)=0

对任意e,fe,fe,fTTTN(e,f)TN(e,f)_TN(e,f)T的取值分两种情况,①TTT投掷使鸡蛋破碎,则后续所需投掷次数为N(e−1,T)N(e-1,T)N(e1,T);②TTT投掷未使鸡蛋破碎,则后续所需投掷次数为N(e,f−T−1)N(e,f-T-1)N(e,fT1)
从而我们得到
N(e,f)T=1+max⁡(N(e−1,T),N(e,f−T−1)N(e,f)_T=1+\max(N(e-1,T),N(e,f-T-1)N(e,f)T=1+max(N(e1,T),N(e,fT1)
从而有动态规划的边界与最优子结构如下

  • N(1,f)=fN(1,f)=fN(1,f)=f
  • N(e,Z\N∗)=0N(e,Z\backslash N^*)=0N(e,Z\N)=0
  • N(e,f)=1+min⁡0≤i≤f−1max⁡(N(e−1,i),N(e,f−i−1))N(e,f)=1+\min\limits_{0\leq i\leq f-1}\max(N(e-1,i),N(e,f-i-1))N(e,f)=1+0if1minmax(N(e1,i),N(e,fi1))

对此可给出代码实现

	public static int dropEgg(int egg, int floor) {
		if (egg == 1)
			return floor;
		if (floor <= 0)
			return 0;
		int min = floor;
		for (int i = 0; i < floor; i++)
			min = Math.min(min, 1 + Math.max(dropEgg(egg - 1, i), dropEgg(egg, floor - i - 1)));
		return min;
	}

这一算法的递归嵌套极为恐怖,甚至不能解决N(2,100)N(2,100)N(2,100)问题。引入Map来存储已计算的N(e,f)N(e,f)N(e,f)可尽可能规避递归栈溢出与大幅减少运算时间。

	private static Map<String, Integer> dropMap = new HashMap<String, Integer>();

	public static int dropEgg(int egg, int floor) {
		String s = egg + " " + floor;
		if (dropMap.containsKey(s))
			return dropMap.get(s);
		if (egg == 1)
			return floor;
		if (floor <= 0)
			return 0;
		int min = floor;
		for (int i = 0; i < floor; i++)
			min = Math.min(min, 1 + Math.max(dropEgg(egg - 1, i), dropEgg(egg, floor - i - 1)));
		dropMap.put(s, min);
		return min;
	}

递归的算法到此为止,接下来是非递归的算法。
为了得到非递归算法,我们需要进一步的分析问题。除了上文所提及的两个边界与最优子结构,我们还需引入两个显然的关系式:

  • N(e,f)≥N(e+1,f)N(e,f)\geq N(e+1,f)N(e,f)N(e+1,f)
  • N(e,f)≤N(e,f+1)N(e,f)\leq N(e,f+1)N(e,f)N(e,f+1)

从而对i≤⌊(f−1)/2⌋i\leq \lfloor (f-1)/2\rfloori(f1)/2,我们有

  • N(e−1,i)≤N(e−1,f−i−1)N(e-1,i)\leq N(e-1,f-i-1)N(e1,i)N(e1,fi1)
  • N(e,f−i−1)≤N(e−1,f−i−1)N(e,f-i-1)\leq N(e-1,f-i-1)N(e,fi1)N(e1,fi1)

从而max⁡(N(e−1,i),N(e,f−i−1))≤max⁡(N(e,i),N(e−1,f−i−1))\max(N(e-1,i),N(e,f-i-1))\leq \max(N(e,i),N(e-1,f-i-1))max(N(e1,i),N(e,fi1))max(N(e,i),N(e1,fi1))
故最优子结构可收束如下:
N(e,f)=1+min⁡0≤i≤⌊(f−1)/2⌋max⁡(N(e−1,i),N(e,f−i−1))N(e,f)=1+\min\limits_{0\leq i\leq \lfloor (f-1)/2\rfloor}\max(N(e-1,i),N(e,f-i-1))N(e,f)=1+0i(f1)/2minmax(N(e1,i),N(e,fi1))
(这一收束同样可优化递归算法,代码参见后附全代码)
i≤⌊(f−1)/2⌋i\leq \lfloor (f-1)/2\rfloori(f1)/2的基础下,考察满足以下两种情况的iii

  • N(e−1,i)=N(e,f−i−1)N(e-1,i)=N(e,f-i-1)N(e1,i)=N(e,fi1)
  • {N(e−1,i)≠N(e,f−i−1)N(e−1,i)=N(e,f−i−2)N(e−1,i+1)=N(e,f−i−1)\left\{\begin{array}{l} N(e-1,i)\neq N(e,f-i-1)\\ N(e-1,i)=N(e,f-i-2)\\ N(e-1,i+1)=N(e,f-i-1) \end{array}\right.N(e1,i)=N(e,fi1)N(e1,i)=N(e,fi2)N(e1,i+1)=N(e,fi1)

对第一种情况,有
{max⁡(N(e−1,i+δ),N(e,f−i−1−δ))≥N(e−1,i+δ)≥N(e−1,i)=max⁡(N(e−1,i),N(e,f−i−1))max⁡(N(e−1,i−δ),N(e,f−i−1+δ))≥N(e,f−i−1+δ)≥N(e,f−i−1)=max⁡(N(e−1,i),N(e,f−i−1))\left\{\begin{array}{l} \max(N(e-1,i+\delta),N(e,f-i-1-\delta))\geq N(e-1,i+\delta)\\ \qquad\geq N(e-1,i)= \max(N(e-1,i),N(e,f-i-1))\\ \max(N(e-1,i-\delta),N(e,f-i-1+\delta))\geq N(e,f-i-1+\delta)\\ \qquad\geq N(e,f-i-1)= \max(N(e-1,i),N(e,f-i-1)) \end{array}\right.max(N(e1,i+δ),N(e,fi1δ))N(e1,i+δ)N(e1,i)=max(N(e1,i),N(e,fi1))max(N(e1,iδ),N(e,fi1+δ))N(e,fi1+δ)N(e,fi1)=max(N(e1,i),N(e,fi1))
从而N(e,f)=1+N(e−1,i)N(e,f)=1+N(e-1,i)N(e,f)=1+N(e1,i)

对第二种情况,由于N(e,f)N(e,f)N(e,f)N∗N^*N上是连续变换的,从而第二种情况与第一种情况互补,故对任意N(e,f)N(e,f)N(e,f),总存在iii符合两种情况其一。
又有
{max⁡(N(e−1,i+1+δ),N(e,f−i−2−δ))≥N(e−1,i+1+δ)≥N(e−1,i+1)=max⁡(N(e−1,i+1),N(e,f−i−2))max⁡(N(e−1,i−δ),N(e,f−i−1+δ))≥N(e,f−i−1+δ)≥N(e,f−i−1)=max⁡(N(e−1,i),N(e,f−i−1))\left\{\begin{array}{l} \max(N(e-1,i+1+\delta),N(e,f-i-2-\delta))\geq N(e-1,i+1+\delta)\\ \qquad\geq N(e-1,i+1)= \max(N(e-1,i+1),N(e,f-i-2))\\ \max(N(e-1,i-\delta),N(e,f-i-1+\delta))\geq N(e,f-i-1+\delta)\\ \qquad\geq N(e,f-i-1)= \max(N(e-1,i),N(e,f-i-1)) \end{array}\right.max(N(e1,i+1+δ),N(e,fi2δ))N(e1,i+1+δ)N(e1,i+1)=max(N(e1,i+1),N(e,fi2))max(N(e1,iδ),N(e,fi1+δ))N(e,fi1+δ)N(e,fi1)=max(N(e1,i),N(e,fi1))
从而
N(e,f)=1+max⁡(N(e−1,i),N(e,f−i−1))N(e,f)=1+\max(N(e-1,i),N(e,f-i-1))N(e,f)=1+max(N(e1,i),N(e,fi1))

故对满足以下条件的i≤⌊(f−1)/2⌋i\leq \lfloor (f-1)/2\rfloori(f1)/2

  • N(e−1,i)=N(e,f−i−1)N(e-1,i)=N(e,f-i-1)N(e1,i)=N(e,fi1)
  • {N(e−1,i)≠N(e,f−i−1)N(e−1,i)=N(e,f−i−2)N(e−1,i+1)=N(e,f−i−1)\left\{\begin{array}{l} N(e-1,i)\neq N(e,f-i-1)\\ N(e-1,i)=N(e,f-i-2)\\ N(e-1,i+1)=N(e,f-i-1) \end{array}\right.N(e1,i)=N(e,fi1)N(e1,i)=N(e,fi2)N(e1,i+1)=N(e,fi1)

N(e,f)=1+max⁡(N(e−1,i),N(e,f−i−1))N(e,f)=1+\max(N(e-1,i),N(e,f-i-1))N(e,f)=1+max(N(e1,i),N(e,fi1))

由此我们得到:

  • N(1,f)=fN(1,f)=fN(1,f)=f
  • N(e,Z\N∗)=0N(e,Z\backslash N^*)=0N(e,Z\N)=0
  • N(e,f)=1+max⁡(N(e−1,i),N(e,f−i−1))N(e,f)=1+\max(N(e-1,i),N(e,f-i-1))N(e,f)=1+max(N(e1,i),N(e,fi1))
    此处i≤⌊(f−1)/2⌋i\leq \lfloor (f-1)/2\rfloori(f1)/2满足
    N(e−1,i)=N(e,f−i−1)N(e-1,i)=N(e,f-i-1)N(e1,i)=N(e,fi1)
    {N(e−1,i)≠N(e,f−i−1)N(e−1,i)=N(e,f−i−2)N(e−1,i+1)=N(e,f−i−1)\left\{\begin{array}{l} N(e-1,i)\neq N(e,f-i-1)\\ N(e-1,i)=N(e,f-i-2)\\ N(e-1,i+1)=N(e,f-i-1) \end{array}\right.N(e1,i)=N(e,fi1)N(e1,i)=N(e,fi2)N(e1,i+1)=N(e,fi1)

由此可得代码实现

	public static int[][] dropEggArray(int egg, int floor) {
		int[][] eggFloorArray = new int[eggNum][floorNum + 1];
		for (int j = 0; j <= floorNum; j++)
			eggFloorArray[0][j] = j;
		for (int i = 0; i < eggNum; i++)
			eggFloorArray[i][0] = 0;
		for (int i = 1; i < eggNum; i++)
			for (int j = 1; j <= floorNum; j++)
				for (int k = j - 1 >> 1; k >= 0; k--)
					if (eggFloorArray[i - 1][k] == eggFloorArray[i][j - k - 1]
							|| (eggFloorArray[i - 1][k] == eggFloorArray[i][j - k - 2]
									&& eggFloorArray[i - 1][k + 1] == eggFloorArray[i][j - k - 1])) {
						eggFloorArray[i][j] = 1 + Math.max(eggFloorArray[i - 1][k], eggFloorArray[i][j - k - 1]);
						break;
					}
		return eggFloorArray;
	}

全量代码如下

import java.util.HashMap;
import java.util.Map;

public class Egg {
	private static final int eggNum = 5;
	private static final int floorNum = 2500;

	public static void main(String[] args) {
		long time = System.currentTimeMillis();
		int[][] dropEggArray = dropEggArray(eggNum, floorNum);
		System.out.println("N=" + dropEggArray[eggNum - 1][floorNum]);
		System.out.println("非递归算法耗时" + (-time + (time = System.currentTimeMillis())) + "ms");
		System.out.println("N=" + dropEgg(eggNum, floorNum));
		System.out.println("递归算法耗时" + (-time + (time = System.currentTimeMillis())) + "ms");

	}

	private static Map<String, Integer> dropMap = new HashMap<String, Integer>();

	public static int dropEgg(int egg, int floor) {
		String s = egg + " " + floor;
		if (dropMap.containsKey(s))
			return dropMap.get(s);
		if (egg == 1)
			return floor;
		if (floor <= 0)
			return 0;
		int min = floor;
		for (int i = 0; i <= floor >> 1; i++)
			min = Math.min(min, 1 + Math.max(dropEgg(egg - 1, i), dropEgg(egg, floor - i - 1)));
		dropMap.put(s, min);
		return min;
	}

	public static int[][] dropEggArray(int egg, int floor) {
		int[][] eggFloorArray = new int[eggNum][floorNum + 1];
		for (int j = 0; j <= floorNum; j++)
			eggFloorArray[0][j] = j;
		for (int i = 0; i < eggNum; i++)
			eggFloorArray[i][0] = 0;
		for (int i = 1; i < eggNum; i++)
			for (int j = 1; j <= floorNum; j++)
				for (int k = j - 1 >> 1; k >= 0; k--)
					if (eggFloorArray[i - 1][k] == eggFloorArray[i][j - k - 1]
							|| (eggFloorArray[i - 1][k] == eggFloorArray[i][j - k - 2]
									&& eggFloorArray[i - 1][k + 1] == eggFloorArray[i][j - k - 1])) {
						eggFloorArray[i][j] = 1 + Math.max(eggFloorArray[i - 1][k], eggFloorArray[i][j - k - 1]);
						break;
					}
		return eggFloorArray;
	}
}

对测试数据e=5,f=2500e=5,f=2500e=5,f=2500的运行结果如下

运行结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值