清晰解题:扔鸡蛋问题

一幢 100 层的大楼,给你两枚鸡蛋。假设,在第 n 层扔下鸡蛋,鸡蛋不碎,那么从第 n-1 层扔鸡蛋,鸡蛋也不会碎。两个鸡蛋一模一样,不碎的话可以扔无数次。目标是利用这两个鸡蛋找出临界楼层 t , 使得鸡蛋从 t 层扔下不会碎, 从 t+1 层扔下会碎。

现要求回答, 最少需要多少次尝试, 才能保证在最坏的情况下,找到楼层 t , 且需要给出尝试的策略。

  • 明确问题
    • 题目要求是考虑 在策略可能遭遇最坏情况下尝试次数最少。 这个地方有些绕,但是必须搞清楚。

    • 举个例子, 如果你用二分法,第一次把鸡蛋从50层扔下去, 最坏的情况是临界层在49层, 第一个鸡蛋碎了,第二个鸡蛋必须得从第一层楼找起。 因此这种策略最坏情况下的尝试次数是50次, 如果你决定采用这个策略, 那你针对题目的答案就是50

    • 现在的目标是找到一个最优策略, 该策略所对应的最坏情况, 比其他策略所对应的最坏情况所需要尝试的次数都要少

明确了问题以后,首先 可以获得一个直觉性的推论, 那就是, 最优的策略必然是用第一个鸡蛋先进行跳跃性的尝试, 如果中间碎了, 就可以用第二个鸡蛋,在剩余的范围内,锁定答案

  • 1.数学猜想反推法
    • 假设题目所要求的策略存在,该策略可以保证在最坏情况下至多需要扔x次鸡蛋就可以找出临界楼层

    • 此时可以将问题转化为:如果只允许扔x次鸡蛋 能确定的最高的楼层是多少。

    • 现在思考, 第一次鸡蛋应该从哪个楼层扔下?

      • 因为此时我们有两个鸡蛋, 为了在有限次数内检查尽可能高的楼层。 第一次扔的时候,我们会希望能尽可能高一些 如果鸡蛋没碎,我们就能排除掉越多的楼层。 但是很显然, 不能过高, 因为万一鸡蛋碎了, 第二个鸡蛋就只能从第一层开始尝试起。

      • 那么第一次扔的楼层最高可以选几呢?

      • 是x, 因为我们总要考虑最坏情况, 当我们从x层扔下的时候, 最坏的情况有可能是临界层为x-1, 此时就需要尝试x次,才能找到这个临界层。 如果第一次尝试的楼层比x大, 显然会导致最大尝试次数超过x , 不符合我们的假设。

    • 回到我们的问题, 现在知道了在最大尝试次数为x次时, 最优策略第一次必然只能从x层扔下的推论后, 我们最大能检查的楼层是多少。

      • 由于第一次从x层扔下时, 我们已经用掉了一次尝试机会, 此时只能再尝试x-1次。 这样我们可以第二次尝试的楼层数可以向上增加x-1层, 即为 x + (x-1).

      • 如果再高,例如第二次我们从2x层扔下, 我们在最坏情况下(临界楼层为2x-1),需要尝试的次数会变成x+1, 不符合我们的假设。

      • 这样依次类推, 第三次向上的增量就只能为 x-3, 第四次向上的增量就只能为 x-4, …, 最后一次向上的增量为 1

      • 此时我们可以得出,如果只能尝试x次, 我们能检测的最高楼层为 x + ( x − 1 ) + ( x − 2 ) + ( x − 3 ) . . . . + 1 = ( x ∗ ( x + 1 ) ) / 2 x+ (x-1) +(x-2) + (x-3)....+1 = (x*(x+1))/2 x+(x1)+(x2)+(x3)....+1=(x(x+1))/2

此时答案就出来了, 我们有100层楼, 则有

x*(x+1)/2 >=100, 解出来 x >= 14

  • 2.动态规划穷举法

    • 现在考虑动态规划解法, 根据上面的分析,其实可以发现, 第一次尝试的楼层, 会对最终的结果产生直接的影响。 现在 设 f(m, n) 为m个鸡蛋, n层楼时, 在最坏情况下的最少尝试次数。 如果第一次尝试从x层楼开始, 则 f ( m , n ) = 1 + m a x ( f ( m − 1 , x − 1 ) , f ( m , n − x ) ) f( m,n ) = 1+ max ( f(m-1, x-1) , f(m, n-x)) f(m,n)=1+max(f(m1,x1)f(m,nx))
    • f ( m − 1 , x − 1 ) f(m-1, x-1) f(m1,x1)对应的是第一次尝试鸡蛋被摔碎的情况
    • f ( m , n − x ) f(m, n-x) f(m,nx)对应的是 第一次尝试鸡蛋没被摔碎的情况

    接下来,只需要编写动态规划程序, 将每一种x都尝试一次, 计算出最小的结果即可

这里给出Java代码


public class EggDropPuzzle {
	public static void main(String[] args) {
		int floors = 100;
		int eggs = 2;
		
		System.out.println(computeMinDropsInWorstCase(eggs, floors));
	}
	
	// A utility function to get maximum of two integers
    static int max(int a, int b) { return (a > b)? a: b; }

	private static int computeMinDropsInWorstCase(int eggs, int floors) {
		int table[][]=new int[eggs+1][floors+1];
		
		
		// boundary condition:
		// if no floors or 1 floors, only need 0 trails or 1 trails
		
		for (int i = 0; i <= eggs; i++) {
			table[i][1] = 1;
			table[i][0] = 0;
		}
		
		
		// if only one egg,   f(1,k) = k
		
		for (int j = 0; j <= floors; j++) {
			table[1][j] = j;
		}
		
		// for the rest of cases
		// f( eggs, floors) = 1+ Max(f( eggs-1 , floors-1), f( eggs, floors-x))
				// x is the floor number we choose to drop for current attempt 
				// range of i = 1,2,.....,floors,
		for(int i = 2; i <= eggs; i++)
		{
			for (int j = 2; j <= floors; j++) {
				
				table[i][j] = Integer.MAX_VALUE;  // so important
				
				for (int floorTriedFirst = 1; floorTriedFirst <= j; floorTriedFirst++) {
					int res = 1+max(table[i-1][floorTriedFirst-1], 
									table[i][j-floorTriedFirst]);  
					if(res < table[i][j])
					{
						table[i][j] = res;
					}
				}
				
			}
		}
		
		return table[eggs][floors];
	}

}

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 9
    评论
汽车加油问题是一个经典的贪心算法问题。假设有一辆汽车要从起点出发到达终点,途中需要经过n个加油站。已知汽车在满油的情况下最多可以行驶d距离,每个加油站离起点的距离为ai,到达该加油站需要加bi升油。请问汽车从起点出发是否能到达终点,如果能,最少需要加多少升油? 解题思路如下: 1.定义一个变量cur表示当前汽车的油量,初始为0,定义一个变量ans表示加油的总量,初始为0。 2.从起点开始遍历每个加油站,假设当前到达第i个加油站。 3.判断当前汽车是否能到达下一个加油站,如果能,则不需要加油,直接到达下一个加油站。如果不能,则需要在当前加油站加油,加油量为到达下一个加油站所需的最少油量,即bi。 4.将ans加上加油量bi,cur减去加油量bi,表示汽车在当前加油站加了bi升油后,剩余油量为cur。 5.重复步骤2-4,直到到达终点。 6.如果汽车能到达终点,则返回ans,否则返回-1表示无法到达终点。 代码实现如下(假设ai是一个升序的数组): ```python def min_refuel_stops(target, startFuel, stations): n = len(stations) pq = [] # 定义一个优先队列,存储每个加油站能提供的油量(负数,因为要从大到小取) ans = 0 cur = startFuel prev = 0 # 表示上一个加油站的位置 for i in range(n): distance = stations[i][0] - prev # 表示到达当前加油站需要的距离 while cur < distance: # 如果当前油量到不了下一个加油站 if not pq: # 如果优先队列为空,表示无法到达终点 return -1 cur += -heapq.heappop(pq) # 从优先队列中取出能提供的最大油量,加到当前油量中 ans += 1 # 加油次数加1 cur -= distance # 到达下一个加油站,当前油量减去到达该加油站需要的油量 prev = stations[i][0] # 更新上一个加油站的位置 heapq.heappush(pq, -stations[i][1]) # 将当前加油站能提供的油量加入优先队列 distance = target - prev # 到达终点需要的距离 while cur < distance: # 如果当前油量到不了终点 if not pq: # 如果优先队列为空,表示无法到达终点 return -1 cur += -heapq.heappop(pq) # 从优先队列中取出能提供的最大油量,加到当前油量中 ans += 1 # 加油次数加1 return ans ``` 这样,就可以用贪心算法决汽车加油问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值