375. 猜数字大小 II
我们正在玩一个猜数游戏,游戏规则如下:
我从 1 到 n 之间选择一个数字。
你来猜我选了哪个数字。
如果你猜到正确的数字,就会 赢得游戏 。
如果你猜错了,那么我会告诉你,我选的数字比你的 更大或者更小 ,并且你需要继续猜数。
每当你猜了数字 x 并且猜错了的时候,你需要支付金额为 x 的现金。如果你花光了钱,就会 输掉游戏 。
给你一个特定的数字 n ,返回能够 确保你获胜 的最小现金数,不管我选择那个数字 。
题解:
本题和测试次数一题比较相似,都是区间DP问题,且都有一个很恶心的点----最差运气与最优策略。
- 我们先来理解一下最差运气,顾名思义,即你的运气很差,不会让你一上来就刚好猜到答案。而这里的“确保你获胜的最小现金数”其实就体现了“最差运气”问题,因为当我们求出我们最差运气的情况下所需的现金数量时,拿着这个现金数去猜数字是一定足够的。
- 我们再来理解一下最优策略。由于我们要猜的那个数到底是什么我们是不知道的,即答案我们是掌控不了的,但是我们每次要猜哪一个数是我们可以选择的,因此我们要选择一种最优的策略,通过定制一套每次猜哪一个数的方案,来满足不论答案是哪一个数,我这套方案都是最优的。通过制定最优策略,使得我们可以得到一个最小现金数。且最优策略是要在最差运气的前提之下才有存在的意义,否则运气很好的话,根本不需要最优策略。因此我们说最差运气是最优策略的前提,最优策略又是对最差运气的弥补性操作。
理解了上述两个思想后,我们再来看一下这题。
- 根据“确保你获胜 的最小现金数”,我们就可以推出是最差运气下的最优策略选择问题。我们要求的是1-n内“确保你获胜
的最小现金数”,因此我们不妨设dp[i][j]
为i -> j
内“确保你获胜 的最小现金数”,因此dp[1][n]
即我们所要答案。 - 我们进而要确定状态方程,由于我们每次猜哪个数是我们可以掌控的,因此若我们第一次猜
x
,那么有: dp[i][j] = x + Math.max(dp[i][x-1],dp[x+1][j]) ;
- 之所以这里取max,就是因为最差运气的体现,我们要考虑运气最背的情况,因为运气是我们不能掌控的因素,而选择哪个数是我们可以掌控的。因此我们需要对x进行枚举,选出最优解,即所需现金数最少的情况。
- 所以即:
dp[i][j] = Math.min(dp[i][j],x + Math.max(dp[i][x-1],dp[x+1][j]))
;
因此最后dp[1][n]即为所求。
代码:
class Solution {
public int getMoneyAmount(int n) {
int[][] dp = new int[n+2][n+1];//开大一点数组防止对数组越界进行讨论了
for(int i=n;i>=1;i--){
for(int j=i;j<=n;j++){
if(i==j){
dp[i][j] = 0;
}
else{
dp[i][j] = Integer.MAX_VALUE;
for(int x=i;x<=j;x++){
dp[i][j] = Math.min(dp[i][j],x + Math.max(dp[i][x-1],dp[x+1][j]));
}
}
}
}
return dp[1][n];
}
}