经典DP:测试鸡蛋承受力

现有鸡蛋N个,M层楼,要测试这批鸡蛋的承受力, 即测试这批鸡蛋最多从几楼扔下去后没事,最少测试几次能保证测出蛋的承受力。
 

 

这题要“最少”“保证”测出这批蛋的承受力。因此,我们要找到所有方式的最坏情况中次数最小的。

代码思维上可以这么理解

for 方式i in 所有方式:
    for 情况j in 所有情况:
        通过找到所有情况中最大次数,找到最坏情况
    通过比较最坏情况的最小值,找到最好的方式

比如:我可以每次一层层扔,也可以每次两层层扔。但是,如果现在在第二层,那么扔一次就到了,如果在100层,那要扔100次。因此要考虑最坏情况,就像考虑时间复杂度最小的算法一样。

很明显,只要扔一个鸡蛋下去,就能判断承受力>或< 此楼层,从此排除另一半楼层,这跟二分不是很像么!
可能很多人上来就要用二分法。但是,这就是这道题的经典之处之一了——这不像经典的二分法可以进行,因为鸡蛋个数有限。如果鸡蛋只有1个,你从m/2楼丢下去碎了,没鸡蛋了,那还测什么。。。所以二分法只是鸡蛋个数足够时的左临界值。

 

因此很容易想到搜索,如果用搜索,第一个蛋从第k层扔下去,有100种可能性,然后往剩余楼层扔。时间复杂度接近O(m^n),指数级的复杂度显然打扰了。

还是先看看状态,如果从第k层丢一个鸡蛋下去,会有两种情况

1. 蛋碎了: 那么下一次需要测[原最底层, k-1], 若每次把最底层看成1,那么,需要测k-1层楼。蛋数-1

2. 蛋不碎:那么下一次需要测[k +1, 原最顶层],若设最顶层为m,需要测m- k层楼。蛋数不变

而且,每次丢一个蛋后,都会有新的最底层跟最高层,即新的第1层跟第m层,这种子结构容易考虑到动态规划了

由于倒着考虑反而难,因为最后一个蛋比较特殊,必须保证它碎的一瞬间是找到楼层,也就是说,一层层丢,所以我们从倒数第二个蛋考虑,发现前面的状态也可以用,就是碎不碎的问题。

由于题目给出变量有两个,一般来说,状态也至少有两个变量。

设剩余需要测的层数为m,此时可以用层数来代表状态,设f(m, n)为剩余m层,n个鸡蛋下,保证测出承受力的最小次数

状态为f(m, n) = min( 之前最小的方式也即f(m, n),max(f(k-1, n-1), f(m-k, n)))  其中n > 1

注意边界: 

这式子也很容易看出边界之一,即每个f(m, n)必须赋最大的初值(可以是无限大),保证能找到最小的f(m, n)

另外,还有一个边界,就是,只有一个鸡蛋时,只能一层层扔

技巧:对所有f(m, n)赋值层数m,可以同时解决两个问题,因为此初始化的值是一层层丢的值,也是最大值。

 

下面给出Java版的代码:

import java.util.Scanner;

public class eggsLimit {
	public int eggsPrint(int M, int N){
		if(M <= 1 || N < 1)
			return 0;
		
		int[][] array = new int[M + 1][N + 1];
		//必须先初始化,因为会用到边界的数据,同时,能把其他值初始化,数组默认为0,Min肯定是0
		//此初始化的值刚好是此层的最大值, 你愿意分为边界n = 1时然后所有初始值为INF也行
		for(int m = 1; m <= M; m++){
			for(int n = 1;  n <= N; n++){
				//把每层最大次数初始化为层数
					array[m][n] = m;
			}
		}
		

			for(int m = 1; m <= M; m++){
				for(int n = 2;  n <= N; n++){
					//枚举从第几层丢
					for(int k = 1; k <= m; k++){
						//每层找到最大的a[m][n],代表最坏的可能性,所有a[m][n]中找到最小的。代表最好的选k方式 
						array[m][n] = Math.min(array[m][n], 1 + Math.max(array[k - 1][n - 1], array[m - k][n]));
					}
			}
		}
		return array[M][N];
	}

	public static void main(String arg[]){
		eggsLimit e = new eggsLimit();
		Scanner scan = new Scanner(System.in);
		int M = scan.nextInt();
		int N = scan.nextInt();
		int result = e.eggsPrint(M, N);
		System.out.print(result);
	}

}

时间复杂度为O(M*M*N),空间复杂度为O(M*N)。由于每次只需要每种情况的最坏情况比较,若使用滚动数组,可以使空间复杂度降到O(M),具体代码就不再给出

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值