猜数字大小 II
- lc - 375 猜数字大小 II
- 题目描述
- 暴力递归 + 记忆化搜索
- 代码演示
- 动态规划
- 动态规划
lc - 375 猜数字大小 II
题目描述
我们正在玩一个猜数游戏,游戏规则如下:
我从 1 到 n 之间选择一个数字。
你来猜我选了哪个数字。
如果你猜到正确的数字,就会 赢得游戏 。
如果你猜错了,那么我会告诉你,我选的数字比你的 更大或者更小 ,并且你需要继续猜数。
每当你猜了数字 x 并且猜错了的时候,你需要支付金额为 x 的现金。如果你花光了钱,就会 输掉游戏 。
给你一个特定的数字 n ,返回能够 确保你获胜 的最小现金数,不管我选择那个数字 。
示例1:
输入:n = 10
输出:16
解释:制胜策略如下:
- 数字范围是 [1,10] 。你先猜测数字为 7 。
- 如果这是我选中的数字,你的总费用为 $0 。否则,你需要支付 $7 。
- 如果我的数字更大,则下一步需要猜测的数字范围是 [8,10] 。你可以猜测数字为 9 。
- 如果这是我选中的数字,你的总费用为 $7 。否则,你需要支付 $9 。
- 如果我的数字更大,那么这个数字一定是 10 。你猜测数字为 10 并赢得游戏,总费用为 $7 + $9 = $16 。
- 如果我的数字更小,那么这个数字一定是 8 。你猜测数字为 8 并赢得游戏,总费用为 $7 + $9 = $16 。
- 如果我的数字更小,则下一步需要猜测的数字范围是 [1,6] 。你可以猜测数字为 3 。
- 如果这是我选中的数字,你的总费用为 $7 。否则,你需要支付 $3 。
- 如果我的数字更大,则下一步需要猜测的数字范围是 [4,6] 。你可以猜测数字为 5 。
- 如果这是我选中的数字,你的总费用为 $7 + $3 = $10 。否则,你需要支付 $5 。
- 如果我的数字更大,那么这个数字一定是 6 。你猜测数字为 6 并赢得游戏,总费用为 $7 + $3 + $5 = $15 。
- 如果我的数字更小,那么这个数字一定是 4 。你猜测数字为 4 并赢得游戏,总费用为 $7 + $3 + $5 = $15 。
- 如果我的数字更小,则下一步需要猜测的数字范围是 [1,2] 。你可以猜测数字为 1 。
- 如果这是我选中的数字,你的总费用为 $7 + $3 = $10 。否则,你需要支付 $1 。
- 如果我的数字更大,那么这个数字一定是 2 。你猜测数字为 2 并赢得游戏,总费用为 $7 + $3 + $1 = $11 。
在最糟糕的情况下,你需要支付 $16 。因此,你只需要 $16 就可以确保自己赢得游戏。
示例2:
输入:n = 1
输出:0
解释:只有一个可能的数字,所以你可以直接猜 1 并赢得游戏,无需支付任何费用。
示例3:
输入:n = 2
输出:1
解释:有两个可能的数字 1 和 2 。
- 你可以先猜 1 。
- 如果这是我选中的数字,你的总费用为 $0 。否则,你需要支付 $1 。
- 如果我的数字更大,那么这个数字一定是 2 。你猜测数字为 2 并赢得游戏,总费用为 $1 。
最糟糕的情况下,你需要支付 $1 。
提示:
1 <= n <= 200
暴力递归 + 记忆化搜索
比较容易想到的做法为使用「递归」进行求解。
设计递归函数为 int dfs(int l, int r) 传入参数 l 和 r 代表在范围
内进行猜数,返回值为在[l,r]
内猜中数字至少需要多少钱。
我们可决策的部分为「选择猜哪个数」,而不可决策的是「选择某个数之后(假设没有猜中),真实值会落在哪边」。
因此为求得「最坏情况下最好」的结果,我们应当取所有的
中的最小值。
最后,为减少重复计算,我们需要在「递归」基础上加入记忆化搜索。
代码演示
static int[][]cache = new int[201][201];
public int getMoneyAmount1(int n) {
return dfs(1, n);
}
/**
* 记忆化搜索
* @param l
* @param r
* @return
*/
public int dfs(int l,int r){
if (l >= r){
return 0;
}
if (cache[l][r] != 0){
return cache[l][r];
}
int ans = Integer.MAX_VALUE;
for (int x = l;x <= r;x++){
int cur = Math.max(dfs(l,x - 1),dfs(x + 1,r)) + x;
ans = Math.min(cur,ans);
}
cache[l][r] = ans;
return ans;
}
动态规划
同样能够通过「递推」来进行求解。
通过「记忆化搜索」的递归过程,我们发现,在求解 [l,r] 的最小成本时,需要依赖于 [l,i−1]和 [i+1,r] 这样的比 [l,r]更小的区间。
这引导我们使用「区间 DP」进行求解,对「区间 DP」不了解的同学可以先看 「区间 DP」入门题 。
定义 f[l][r]为考虑在 [l,r] 范围内进行猜数的最小成本。
不失一般性的考虑 f[l][r] 该如何计算。同样的,我们可决策的部分为「选择猜哪个数 x」,而不可决策的是「选择某个数 x 之后(假设没有猜中),真实值在落在哪边」。
我们对本次选择哪个数进行讨论,假设本次选择的数值为 x ( l<=x<=r),则有 cur=max(f[l][x−1],f[x+1][r])+x
最终的 f[l][r] 为所有可选的数值 x 中的最小值。
代码:
/**
* 动态规划
* @param n
* @return
*/
public int getMoneyAmount(int n) {
int[][] ans = new int[201][201];
for (int len = 2;len <= n;len++){
for (int l = 1;l + len - 1 <= n;l++){
int r = l + len - 1;
ans[l][r] = Integer.MAX_VALUE;
for (int k = l;k <= r;k++){
int cur = Math.max(ans[l][k -1],ans[k + 1][r]) + k;
ans[l][r] = Math.min(ans[l][r],cur);
}
}
}
return ans[1][n];
}
动态规划
力扣 1155. 掷骰子等于目标和的方法数