问题描述
我们正在玩一个猜数游戏,游戏规则如下:
我从 1 到 n 之间选择一个数字,你来猜我选了哪个数字。
每次你猜错了,我都会告诉你,我选的数字比你的大了或者小了。
然而,当你猜了数字 x 并且猜错了的时候,你需要支付金额为 x 的现金。直到你猜到我选的数字,你才算赢得了这个游戏。
示例:
1 | n = 10, 我选择了8. |
给定一个 n ≥ 1,计算你至少需要拥有多少现金才能确保你能赢得这个游戏。
致谢:
特别感谢 @agave 和 @StefanPochmann 添加了这道题目,并且提供了所有测试用例。
解题思路
解答本题时,第一思路是采用快速查找算法,但是步长少并意味着支付的金额最少。那如何解决本题了?当没有思路的时候,最笨的方法就是最好的方法,列举一些数字,看看其中有没有什么规律:
- 当
n = 1
时,显然cost = 0
- 当
n = 2
时,即[1, 2]
,显然cost=1
- 当
n = 3
时,即[1, 2, 3]
,只需要猜中间数字2
就能知道结果,因为比2
大的只有一个,比2
小的也只有一个,所以cost=2
- 当
n = 4
时,即[1, 2, 3, 4]
。四个数字显然不能一眼看出最少cost
,那就一个一个来计算吧:- 如果先猜
1
,如果猜中,则cost=1
,否则数字在[2-4]
之间,这时需花费cost = 1 + Cost(2, 4) = 1 + 3 = 4
。这时,应该取cost=4
,为什么了,因为如果你要赢得比赛,就必须至少要带¥4
,所以取大 - 如果先猜
2
,则cost = 2 + Cost(3,4) = 2 + 3 = 5
- 如果先猜
3
,则cost = 3 + Cost(1,2) = 3 + 1 = 4
- 如果先猜
4
,则cost = 4 + Cost(1,3) = 4 + 2 = 6
- ==综上所列==,当有4个数字时,至少要带
¥4
(为啥?因为按照上面的方案,至少要带够¥4
元才有可能赢得这个游戏呀!)。至此已经可以看出,这是一道求局部最大值,全局最小值的问题,
- 如果先猜
- 当
n = 5
时,即[1, 2, 3, 4, 5]
,同样可以挨个计算每个数字,最终得到最小cost
综上所述,当给定数字n时,可以从1
开始,计算全部至少花费情况,最后取最小花费,伪代码如下:
1 | # 计算[i, j]区间最小花费 |
至此,本题可以采用递归的方法解决,但是算法复杂度较高,不能通过测试。通过观察发现,在递归计算过程中,有很多区间会被重复计算。一种优化策略是保存计算结果,相同计算时直接从缓存中读取。
另外一种解题方法是采用动态规划实现。比如当n=5
时,
初始化一个
n+1
维dp
数组令
i
inrange(2, 6)
,j
inrange(i-1, 0, -1)
, 计算区间[j, i]
的最小花费当i = 2时
- 当j = 1
- 当k = 1,
cost = 1
dp[1][2]=1
- 当k = 1,
- 当j = 1
当i = 3时
当j = 2
- 当k = 2,
cost = 2
dp[2][3]=2
- 当k = 2,
当j = 1
- 当k = 1,
cost = 1 + dp[2][3] =3
- 当k = 2,
cost = 2 + max(dp[1][1], dp[3][3]) = 2
dp[1][3] = 2
- 当k = 1,
当i = 4时
当j = 3
- 当k = 3,
cost = 3
dp[3][4] = 3
- 当k = 3,
当j = 2
- 当k = 2,
cost = 2 + dp[3][4] = 5
- 当k = 3,
cost = 3 + max(dp[2][2], dp[4][4]) = 3
dp[2][4] = 3
- 当k = 2,
当j = 1
- 当k = 1,
cost = 1 + dp[2][4] = 4
- 当k = 2,
cost = 2 + max(dp[1][1], dp[3][4]) = 5
- 当k = 3,
cost = 3 + max(dp[1][2], dp[4][4]) = 4
dp[1][4] = 4
- 当k = 1,
当i = 5时
- 当j = 4
- 当k = 4,
dp[4][5]=4
- 当k = 4,
- 当j = 3
- 当k = [3, 4],
dp[3][5] = 4
- 当k = [3, 4],
- 当j = 2
- 当k = [2, 3, 4],
dp[2][5] = 6
- 当k = [2, 3, 4],
- 当j = 1
- 当k = [1, 2, 3, 4],
dp[1][5] = 4
- 当k = [1, 2, 3, 4],
- 当j = 4
dp[1][5]即为最小花费金额
代码
1 | import sys |