【leetCode】375. 猜数字大小

511 阅读3分钟

这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战

题目

我们正在玩一个猜数游戏,游戏规则如下:

我从 1 到 n 之间选择一个数字。 你来猜我选了哪个数字。 如果你猜到正确的数字,就会 赢得游戏 。 如果你猜错了,那么我会告诉你,我选的数字比你的 更大或者更小 ,并且你需要继续猜数。 每当你猜了数字 x 并且猜错了的时候,你需要支付金额为 x 的现金。如果你花光了钱,就会 输掉游戏 。 给你一个特定的数字 n ,返回能够 确保你获胜 的最小现金数,不管我选择那个数字 。

链接:leetcode-cn.com/problems/gu…

分析

如果不考虑权重(要多少钱)的情况,那么最好的解法就是二分;

然而,我们这里需要考虑要付多少钱,因此二分不是一个明智的选择,而且需要注意这里的题目:

不管我选择那个数字

也就是说,其实我们要准备若干的钱,可能钱没花完,猜到了;但是,总有一个数,可以让我们钱刚好画完的时候猜到。

那么其实这个问题就和数字是多少无关了,这里的目的是:

设计一个算法,求出最少花多少钱,能确认所有数是否是正确答案

我们来模拟一下玩这个游戏来尝试找到规律:

假设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%的用户