我们正在玩一个猜数游戏,游戏规则如下:
我从 1 到 n 之间选择一个数字,你来猜我选了哪个数字。
每次你猜错了,我都会告诉你,我选的数字比你的大了或者小了。
然而,当你猜了数字 x 并且猜错了的时候,你需要支付金额为 x 的现金。直到你猜到我选的数字,你才算赢得了这个游戏。
示例:
n = 10, 我选择了8.
第一轮: 你猜我选择的数字是5,我会告诉你,我的数字更大一些,然后你需要支付5块。
第二轮: 你猜是7,我告诉你,我的数字更大一些,你支付7块。
第三轮: 你猜是9,我告诉你,我的数字更小一些,你支付9块。
游戏结束。8 就是我选的数字。
你最终要支付 5 + 7 + 9 = 21 块钱。
给定 n ≥ 1,计算你至少需要拥有多少现金才能确保你能赢得这个游戏。
思路:这是一道Minimax算法 又名极小化极大算法问题。局部最大值,全局最小值。举例说明:
1、当n = 1
时,显然cost = 0
2、当n = 2
时,即[1, 2]
,显然cost=1
3、当n = 3
时,即[1, 2, 3]
,只需要猜中间数字2
就能知道结果,因为比2
大的只有一个,比2
小的也只有一个,所以cost=2
4、当n = 4
时,即[1, 2, 3, 4]
。四个数字显然不能一眼看出最少cost
,那就一个一个来计算吧。
- 如果先猜
1
,如果猜中,则cost=0
,否则数字在[2-4]
之间,这时需花费cost = 1 + Cost(2, 4) = 1 + 3 = 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
才能确保能赢得这个游戏。
5、当n = 5
时,即[1, 2, 3, 4, 5]
,同样可以挨个计算每个数字,最终得到最小cost
综上所述,当给定数字n时,可以从1
开始,计算全部至少花费情况,最后取最小花费。
因此有[i,j]区间内的局部最大值 local_max = k + max(cost(i, k-1), cost(k+1, j)),取最大值的原因是不知道实际数字是比k大,还是比k小,一定要考虑到所有情况,按最坏情况处理。
最后对每一k得到的local_max取最小值,得到[i,j]区间至少需要多少钱赢得游戏。
注意k是我们可控的,实际数字是我们不可控的。
实际书写代码中,采用记忆数组dp,dp[i][j]表示[i,j]区间至少需要多少钱赢得游戏,把已经得到的数保存下来,减少重复计算。
class Solution {
public:
int helper(int s, int e, vector<vector<int> > &dp){
if(s>=e) return 0;
if(dp[s][e]) return dp[s][e]; //之前已经求出了dp[s][e]
int re=INT_MAX;
for(int i=s; i<=e; ++i){
re=min(re, i + max(helper(s,i-1, dp), helper(i+1, e, dp)));
}
dp[s][e]=re;
return re;
}
int getMoneyAmount(int n) {
vector<vector<int> >dp(n+1, vector<int>(n+1, 0));
return helper(1, n, dp);
}
};