Leetcode |887鸡蛋掉落+踩坑思路分析

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

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

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

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

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

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

示例 1:

输入:K = 1, N = 2
输出:2
解释:
鸡蛋从 1 楼掉落。如果它碎了,我们肯定知道 F = 0 。
否则,鸡蛋从 2 楼掉落。如果它碎了,我们肯定知道 F = 1 。
如果它没碎,那么我们肯定知道 F = 2 。
因此,在最坏的情况下我们需要移动 2 次以确定 F 是多少。
示例 2:

输入:K = 2, N = 6
输出:3
示例 3:

输入:K = 3, N = 14
输出:4

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/super-egg-drop

代码思路:
有一个当前序列(k,n).
如果知道有一个x,是当前的最佳分割位置,同时知道分割后的两个序列(k-1,x-1)和(k,n-x)各自需要的移动次数,那么当前序列的返回值就是1+max((k-1,x-1),(k,n-x))。
进而问题转化为最小化这个max((k-1,x-1),(k,n-x))。分析一下:

  • max((k-1,x-1),(k,n-x))和什么参数有关

    和x有关,而x又取决于k和n的值,这句话不是废话,当鸡蛋的数量不足以每次二分的时候,就必须减少x的值,尽量往下划分。例如:(2,100)=14第一次的移动x=14,就不是x=50。

  • 如何快速有效的找到需要的x。
    这里就采用二分法进行搜索,理由,因为当x左右两边的移动次数相近的时候,一定是最小的时候,(k,n)如果看成一个函数,在k一定的时候函数值随着n的增大而增大,减小而减小,(k-1,x-1),(k,n-x),当x-1减小,n-x必然增大,所以在交点附近的x就是我们要找的x。
    在二分法的过程中,设区间为front-last,x=(front+last)/2,如果(k-1,x-1)< ( k,n-x),就要让front=x,如果(k-1,x-1)> ( k,n-x)就要让last=x,如果(k-1,x-1)= ( k,n-x),就让front=last=x,就是找到了需要的x。终止条件,这里我学习的Leetcode官方代码,官方还是很强的,front+1<last,这里的判断则是因为如果n是一个偶数,那么一定找不到最中间的值,也或者(k-1,x-1), ( k,n-x)两个一定不相等,那么搜索到区间紧挨着的时候,也就没办法继续二分,进而退出循环。

while(front+1<last) {
				x=(front+last)/2;
				result1=dfs(k-1,x-1);
				result2=dfs(k,n-x);
				if(result1>result2) {
					last=x;
				}else if(result1<result2){
					front=x;
				}else {
					front=x;
					last=x;
				}
				
			}

剩下的就很简单了,对于区间长度为1的时候还需要判断究竟是要以front为x还是以last为x。所以还需要判断,Math.min(Math.max(dfs(k-1, front-1), dfs(k, n-front)), Math.max(dfs(k-1, last-1), dfs(k, n-last)))

代码如下:

public int superEggDrop(int K, int N) {
		if(K==1)return N;
		if(N==1)return 1;
		if(N==2)return 2;
		if(N==3)return 2;
		dfs(K,N);
		return  map.get(N*100+K);
    }
	public static int dfs(int k,int n) {
		if(k==1)return n;
		if(n==1)return 1;
		if(n==2)return 2;
		if(n==3)return 2;
		int result1=0;
		int result2=0;
		int result4=0;	
		if(map.containsKey(n*100+k)) {
			return map.get(n*100+k);
		}else {
			
			int front=0;
			int last=n;
			int x=0;
			while(front+1<last) {
				x=(front+last)/2;
				result1=dfs(k-1,x-1);
				result2=dfs(k,n-x);
				if(result1>result2) {
					last=x;
				}else if(result1<result2){
					front=x;
				}else {
					front=x;
					last=x;
				}
				
			}
			result4 = 1 + Math.min(Math.max(dfs(k-1, front-1), dfs(k, n-front)),
                    Math.max(dfs(k-1, last-1), dfs(k, n-last)));

System.out.println(k+" "+n);
			map.put(n*100+k, result4);
		}
		
		return map.get(n*100+k);
	}

结果如下:
在这里插入图片描述
代码分析:
leetcode官方代码的分析很容易懂,所有需要的(k,n)的组合一共有kn个,所以需要记录kn个组合对应的值,每一个组合都进行二分法进行判断,假设所有的节点的值都知道(最简单的组合例如(1,n),(k,1)的值是知道的,进而分支上方的各个组合的值也可以求得,同时每个组合只会进行一次)所以每个组合需要进行(logn)次计算,所以时间复杂度是O(knlogn),空间复杂度O(k*n)。

踩坑分析:

  • 遇到题需要合理分析
    分割点x不一定是n/2,当k较小而n比较大的时候,x会向下取值,同时向下取值的幅度又和n有关,所以如下代码是错误的。判断过于草率。
public static int dfs1(int k,int n1,int n2,int x,int y) {
			
			if(k==1)return n2-n1;
			if(n2-n1==1&&(n2==y&&n1==x)) {
				return 1;
			}else if(n2-n1==1) {
				return 2;
			}
			if(n2==n1&&(n1==x||n2==y)) {
				return 1;
			}else if(n2==n1) {
				return 0;
			}
			int temp=n2-n1+1;
			int half=temp/2;
			int result1=0;
			int result2=0;
			int result3=0;
			int result4=0;
			if(temp%2==1) {
				result1=dfs1(k-1,n1,n1+half-1,x,y);
				result2=dfs1(k,n1+half+1,n2, x,y);
			}else {
				result1=dfs1(k-1,n1,n1+half-2, x, y);
				result2=dfs1(k,n1+half,n2,x,y);
			}
			if(result1>result2) {
				return 1+result1;
			}else {
				return 1+result2;
			}
			
			
		}
  • 二分法是必须的,为了减少搜索的次数
    下面的代码就是超时了。
public static int dfs2(int k,int n) {
		if(k==1)return n;
		if(n==1)return 1;
		if(n==2)return 2;
		if(n==3)return 2;
		int result1=0;
		int result2=0;
		int result3=0;
		int result4=0;
		int mid=n/2+2;
		int min=100000;
		if(map.containsKey(n*100+k)) {
			return map.get(n*100+k);
		}else {
			while(true&&mid>1) {
				mid=mid-1;
				result1=dfs2(k-1,mid-1);
				result2=dfs2(k,n-mid);
				if(result1<result2)result1=result2;
				if(min>=result1) {
					min=result1;
				}else {
					break;
				}
				
			}
			map.put(n*100+k, 1+min);
		}
		return map.get(n*100+k);
	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值