1 问题描述
有 T 层楼,n个鸡蛋,鸡蛋是相同的,临界楼层是指从某个楼层之上抛下来,都会碎,但从这个楼层之下抛下来,都不会碎。没有碎的鸡蛋可以重复使用。试假设能找到这个临界楼层需要抛投的最少次数。
T层,n个鸡蛋, 总是先要任选一层k, 扔下一个鸡蛋;扔下之后有两个结果:
- 没碎: 则需要在高层求解 F(T-k, n)
- 碎了: 则在k层以下求解 F(k-1, n-1)
假设我们在第 k 层扔下第一个鸡蛋,最坏的情况下,我们需要扔多少次:
cost(k, T, n) = max{F(T-k, n), F(k-1, n-1)} + 1
我们把每一层,遍历一下最坏情况下所有方案的,最少扔鸡蛋的次数:
F(T, n) = min{cost(1, T, n), cost(2, T, n), …, cost(k, T, n), …, cost(T, T, n)}
2 实现
简单一点,我们直接采用递归求解。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
|
#define INF (1<<30)
int F(int T, int egg);
int G_Cache[1000][20];
//方法一:递归求解(记忆化)
int cost(int k, int T, int egg)
{
return std::max(F(T-k, egg), F(k-1, egg-1)) + 1;
}
int F(int T, int egg) {
// only 1 egg, return layers
if (egg == 1) {
return T;
}
// 0 layer or 1 layer
if (T == 0 || T == 1) {
return T;
}
//命中缓存
if (G_Cache[T][egg]) {
return G_Cache[T][egg];
}
int ans = INF;
for(int k = 1; k<=T; ++k) {
int times = cost(k, T, egg);
ans = std::min(ans, times);
}
//加入缓存
G_Cache[T][egg] = ans;
return ans;
}
//递归求解(记忆化递归)
void TestRecursive(){
memset(G_Cache, 0, sizeof(G_Cache));
// 100 layer, 2 eggs, 最多只需要 14 times
int times = F(100, 2);
printf("%d\n", times);
}
/**
* 方法二: 递推求解
* T层,N个鸡蛋,最多需要扔多少次
* -使用递推公式, 打表求解:
* -时间复杂度 O(T*T*N)
* -空间复杂度 O(T*N)
* x当然空间,还可以进一步优化,我们发现递推公式,鸡蛋只有 egg/egg-1,
* x每次计算只前一列+当前列有关, 使用滚动数组进行优化:
* O(2T)
*/
int Derivate(int T, int egg_size)
{
const int ROW = T + 2;// 楼层
const int COL = egg_size + 2;//鸡蛋个数
const int MAX_INF = 0x7f7f7f7f;
// 二维数组 dp[ROW][COL],每个元素初始化为 MAX_INF
vector<vector<int>> dp(ROW, vector<int>(COL, MAX_INF));
// 0层,只需要0次
for(int z = 0; z <= egg_size; z++){ dp[0][z] = 0;}
// 1层, n>=1个蛋都只需要1次
for(int z = 1; z <= egg_size; z++){ dp[1][z] = 1;}
// 1个蛋,k层就需要k次(只能每一层去测试)
for(int k = 1; k <= T; k++){ dp[k][1] = k;}
for (int egg = 2; egg <= egg_size; egg++) {
for (int t = 2; t <= T; t++) {
//min_cost 表示T层,N个鸡蛋,最少需要扔多少次
int min_cost = MAX_INF;
for (int k = 1; k <= t; k++) {
int times = std::max(dp[t - k][egg], dp[k - 1][egg - 1]) + 1;
min_cost = std::min(min_cost, times);
}
dp[t][egg] = min_cost;
}
}
return dp[T][egg_size];
}
//方法二: 递推求解
void TestDerivate() {
// 100 layer, 2 eggs, 最多只需要 14 times
int times = Derivate(100, 2);
printf("%d\n", times);
}
int main()
{
TestDerivate();
return 0;
}
|
总结
很多问题我们需要先分析,简单情况; 将复杂的问题划分为更小的问题,
如果问题还不可解,继续划分;直到划分为最简单的场景。
这种将大化小, 逐个击破的思路,在解决问题上非常有用。
因为复杂问题,都可看出许多小问题的组合;一旦我们掌握了这种思维方式,很多问题就可以迎刃而解了。
比如, 多个点的最小外接圆问题, 背包问题, 海盗分金币问题;都可以用这种思路求解。
参考