这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战」
题目
我们正在玩一个猜数游戏,游戏规则如下:
我从 1 到 n 之间选择一个数字。 你来猜我选了哪个数字。 如果你猜到正确的数字,就会 赢得游戏 。 如果你猜错了,那么我会告诉你,我选的数字比你的 更大或者更小 ,并且你需要继续猜数。 每当你猜了数字 x 并且猜错了的时候,你需要支付金额为 x 的现金。如果你花光了钱,就会 输掉游戏 。 给你一个特定的数字 n ,返回能够 确保你获胜 的最小现金数,不管我选择那个数字 。
分析
如果不考虑权重(要多少钱)的情况,那么最好的解法就是二分;
然而,我们这里需要考虑要付多少钱,因此二分不是一个明智的选择,而且需要注意这里的题目:
不管我选择那个数字
也就是说,其实我们要准备若干的钱,可能钱没花完,猜到了;但是,总有一个数,可以让我们钱刚好画完的时候猜到。
那么其实这个问题就和数字是多少无关了,这里的目的是:
设计一个算法,求出最少花多少钱,能确认所有数是否是正确答案。
我们来模拟一下玩这个游戏来尝试找到规律:
假设n=10
- 此时,我们随机选一个数,不妨定位6,此时我们可以知道是大或者是小,或者恰好是6。
- 如果大,我们从1-5选一个数;如果小,我们从7-10选一个数。
那么,我们最多付出多少钱呢?我们定义在(a,b)区间上,我们最多付出的钱是F(a,b)
此时,我们最多付出的钱,就相当于6 + max(F(1,5),F(7,10))。
我们定义total(X)为选择X时最多付出的钱,这里就得到了第一个递归公式:
total(X) = X + max(F(1,X-1) + F(X+1,n))
因为我们要找确认所有数是否是正确答案,那么我们就从结果里选最小的,按照这个递归思路就能找出:
F(n) = min(total(1),(total(2)),.....,total(n))
到这里我们的递归就有眉目了,此时还剩下一个边界条件。如何确定边界条件呢?
还是模拟上面的游戏,我们这次来考虑边界情况:
1.假设上面定的数比7大,我们在6之后选择了7
那么,此时我们还有3个数可以选:[8,9,10]
此时,我们只要选中间的数,就可以确定是哪个了。(三数取中)
2.假设上面定的数比8大,我们在6之后选择了8
此时,我们只有2个选择了:[9,10]
此时,两个数中必然有一个数是正确答案,那么选便宜的那个就可以确定了。(二数取小)
3.还有一个数的情况呢?
这种时候就不需要考虑了,它就是正确的数,不用花冤枉钱了。(一数不取)
根据上面的分析,就可以写出对应的算法了:
- 基于我们知道:从l-r的计算结果是固定的,因此我们做个缓存,来减少重复计算:
private static int[][] record = new int[201][201];
public int getMoneyAmount(int n) {
return dfs(1,n);
}
public int dfs(int l,int r){
if(l>=r) return 0;
if(l == r-1) return l;
if(l == r-2) return l+1;
if(record[l][r]!=0) return record[l][r];
int min = Integer.MAX_VALUE;
for (int i = l; i <= r; i++) {
int cur = Math.max(dfs(l,i-1),dfs(i+1,r))+i;
min = Math.min(cur,min);
}
record[l][r] = min;
return min;
}
结果:
执行用时:8 ms, 在所有 Java 提交中击败了98.88%的用户
内存消耗:35.2 MB, 在所有 Java 提交中击败了100.00%的用户