2个鸡蛋100层楼
问题
有2个鸡蛋和100层楼,鸡蛋从\(\ge x\)层落下会破,从\(\lt x\)层落下不破,问题:最坏情况下,确定\(x\)需要的最少实验次数。
分析
\(x\)可能的取值为\(1,2,3,...,99,100, \gt 100\);
若只有一个鸡蛋,显然\(x=100\)。
现在有2个鸡蛋,考虑第一个鸡蛋在第一次实验中应该从哪层楼落下。
直观的想法是二分从50层落下。
但是若鸡蛋破了,接下来只剩余一个鸡蛋去测试1-49层楼,需要实验次数是49。
在此情况下,实验总次数是50。非常大。
那么第一个鸡蛋在第一次实验中到底应该从哪层楼落下?设在\(k\)层落下:
- 若鸡蛋破了,则剩余1个鸡蛋,\(k-1\)层楼,实验次数是\(k-1\);
- 若鸡蛋没破,则剩余2个鸡蛋,\(100-k\)层楼,实验次数未知;但当前这个问题和原题目就比较类似了;
动态规划
设\(dp[100][2]\)表示100层楼2个鸡蛋所需要的实验次数;我们枚举
第一个鸡蛋可能从哪层楼落下的,则有
\[ dp[100][2] = \min(\max(k-1, dp[100-k][2])) + 1, 1<=k<=100 \]
泛化后:
设\(dp[i][j]\)表示\(i\)层楼\(j\)个鸡蛋所需要的实验次数;我们枚举当前鸡蛋可能从哪层楼落下的,则有
\[ dp[i][j] = \min(\max(dp[k-1][j-1], dp[i-k][j])) + 1, 1<=k<=i \]
观察
我们观察17层2个鸡蛋的结果,运行文末的代码,可以发现:
状态转移路径:
17 2 6 <- 15 2 5 <- 10 2 4 <- 6 2 3 <- 3 2 2 <- 1 2 1 <- 0 2 0
楼层集合 | 答案 | ||||
---|---|---|---|---|---|
1 | 1 | ||||
2, 3 | 2 | 2 | |||
4, 5, 6 | 3 | 3 | 3 | ||
7, 8, 9, 10 | 4 | 4 | 4 | 4 | |
11, 12, 13, 14, 15 | 5 | 5 | 5 | 5 | 5 |
16, 17, … | 6 | 6 | … |
可以发现,对于状态转移而言,表格的每行都是由上一行末尾的楼层转移过来的;
实际实验策略:
2->7->11->14->16->17
第1次把鸡蛋1放在2层,若破了,则总计0+(2-2)=2次;若没破,则继续;
第2次把鸡蛋1放在7层,若破了,则总计1+(7-2)=6次;若没破,则继续;
第3次把鸡蛋1放在11层,若破了,则总计2+(11-7)=6次;若没破,则继续;
第4次把鸡蛋1放在14层,若破了,则总计3+(14-11)=6次;若没破,则继续;
第5次把鸡蛋1放在16层,若破了,则总计4+(16-14)=6次;若没破,则继续;
第6次把鸡蛋1放在17层,总计5+(17-16)=6次;
代码
#!/bin/usr/env python
# -*- coding: utf-8 -*-
__date__ = '1/16/19 15:26'
# i层j球
def solve(i, j):
if dp[i][j] != -1:
return dp[i][j]
# 若一个球,则一层层试
if j == 1:
dp[i][j] = i
path[i][j] = 0, 0
return dp[i][j]
# 若0层,则0次
if i == 0:
path[i][j] = 0, 0
dp[i][j] = 0
return dp[i][j]
# max(第k层鸡蛋破了,第k层鸡蛋没破)
mark_val = i + 1
mark_path = 0, 0
for k in xrange(1, i + 1):
# 使用>而不是>= 意味着优先选择保留鸡蛋
if solve(k-1, j-1) > solve(i-k, j):
tmp_val = solve(k-1, j-1) + 1
tmp_path = k-1, j-1
else:
tmp_val = solve(i-k, j) + 1
tmp_path = i-k, j
if mark_val > tmp_val:
mark_val = tmp_val
mark_path = tmp_path
dp[i][j] = mark_val
path[i][j] = mark_path
# dp[i][j] = min([max(solve(k-1, j-1), solve(i-k, j)) for k in xrange(1, i+1)]) + 1
return dp[i][j]
if __name__ == '__main__':
dp = [[-1 for i in xrange(1000)] for j in xrange(1000)]
path = [[(-1, -1) for i in xrange(1000)] for j in xrange(1000)]
i, j = 17, 2
solve(i, j)
print [(k, dp[k][2]) for k in xrange(i+1)]
print "状态转移路径: "
print i, j, dp[i][j], '<-',
method = [0]
while path[i][j] != (0, 0):
method.append(method[-1] + i - path[i][j][0])
i, j = path[i][j]
print i, j, dp[i][j], '<-',
print
print "实际实验策略: "
print '->'.join([str(i) for i in method[1:]])