347,猜数字大小 II

想了解更多数据结构以及算法题,可以关注微信公众号“数据结构和算法”,每天一题为你精彩解答。也可以扫描下面的二维码关注
在这里插入图片描述

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

我从 1 到 n 之间选择一个数字,你来猜我选了哪个数字。

每次你猜错了,我都会告诉你,我选的数字比你的大了或者小了。

然而,当你猜了数字 x 并且猜错了的时候,你需要支付金额为 x 的现金。直到你猜到我选的数字,你才算赢得了这个游戏。

示例:

n = 10, 我选择了8. 第一轮: 你猜我选择的数字是5,我会告诉你,我的数字更大一些,然后你需要支付5块。

第二轮: 你猜是7,我告诉你,我的数字更大一些,你支付7块。

第三轮: 你猜是9,我告诉你,我的数字更小一些,你支付9块。

游戏结束。8 就是我选的数字。

你最终要支付 5 + 7 + 9 = 21 块钱。

给定 n ≥ 1,计算你至少需要拥有多少现金才能确保你能赢得这个游戏。

答案:

public static int getMoneyAmount(int n) {
    return DP(1, n);
}

public static int DP(int left, int right) {
    if (left >= right)
        return 0;
    int res = Integer.MAX_VALUE;
    for (int i = left; i <= right; i++) {
        int tmp = i + Math.max(DP(left, i - 1), DP(i + 1, right));
        res = Math.min(res, tmp);
    }
    return res;
}

解析:

在数字1-n之间,假如我们选择了x,数组就会分成3个部分,[1,x-1],x,[x+1,n]。那么会有3种情况,第一种我们选中了,所以这时候花费现金最少,第二种是我们选大了,第三种是我们选小了,用函数f(m,n)表示从数字m到n中所花费的最小金额,如果选择了在范围(i,j)中保证所花费最少(i<=x<=j),我们有下面这样一个公式。money=x+max(f(i,x-1)+f(x+1,j));代码第10行我们找的是最大值,因为题目说了至少花费多钱现金才能赢得游戏,我们只需要找到所有的花费最大值中的最小值即可。但是上面代码效率明显不是很高,因为递归的原因会出现重复计算,我们只需要用一个临时数组存储每次计算的结果,防止重复计算即可,我们来优化一下

public static int getMoneyAmount(int n) {
    int[][] table = new int[n + 1][n + 1];
    return DP(table, 1, n);
}

public static int DP(int[][] table, int left, int right) {
    if (left >= right)
        return 0;
    if (table[left][right] != 0)
        return table[left][right];
    int res = Integer.MAX_VALUE;
    for (int i = left; i <= right; i++) {
        int tmp = i + Math.max(DP(table, left, i - 1), DP(table, i + 1, right));
        res = Math.min(res, tmp);
    }
    table[left][right] = res;
    return res;
}

除了递归的写法以外,我们还可以使用动态规划的方式解决,先看一下代码

public int getMoneyAmount(int n) {
    int[][] table = new int[n + 1][n + 1];
    for (int i = 2; i <= n; i++) {
        for (int j = i - 1; j > 0; j--) {
            int globalMin = Integer.MAX_VALUE;
            for (int k = j + 1; k < i; k++) {
                int localMax = k + Math.max(table[j][k - 1], table[k + 1][i]);
                globalMin = Math.min(globalMin, localMax);
            }
            table[j][i] = j + 1 == i ? j : globalMin;
        }
    }
    return table[1][n];
}

前两个for循环会组成一个封闭的空间[j,i],然后第3个for循环再从这个封闭的空间中找出所有花费最大值的最小值即可,这个最小值也只不过是在区间[j,i]之间的,然后通过外面两层的for循环最终会找出区间[1,n]的值。第10行表示如果j和i仅仅相差1的话,那么第3个for循环根本就不会执行,我们要猜最小的才能花费最少,所以选择j。下面来画个图加深一下理解
在这里插入图片描述

这里我们来举个例子简单说下,比如n等于5,为什么只需要6块钱就一定能赢得游戏,结合上面的分析,我们知道当我们先猜2,或者4的时候结果都是6,我们以先猜2来分析一下,

1, 如果选择的是1,我们猜2,说明大了,下一步直接猜1就行了,所以只花了2块钱。

2, 如果选择的是2,我们猜2,说明猜对了,一分没花。

3, 如果选择的是3,我们猜2,说明猜小了,下一步猜4,说明大了,在下一步直接猜3,猜对了,所以总共花了2+4=6块钱。

4, 如果选择的是4,我们猜2,说明猜小了,下一步猜4,猜中了,只花了2块钱。

5, 如果选择的是5,我们猜2,说明猜小了,下一步猜4,又小了,在下一步直接猜5,猜对了,所以总共花了2+4=6块钱。


综上所述,当n等于5的时候,我们只需要6块钱就一定能赢,

思考:

这题我估计很多人都听说过,或者看过类似的这种题,很多时候我们猜这样的题都喜欢从中间来猜,显然通过上面的分析,如果我们从中间猜不一定会有最优的结果,比如当n=5的时候,当我们选择4,或者5的时候,如果我们从中间来猜,先猜3,小了,再猜4,这时候无论是猜对了还是猜小了,所花费的肯定是大于6的,很明显不是最优解。


在这里插入图片描述

posted @ 2020-09-26 20:39  数据结构和算法  阅读(203)  评论(0编辑  收藏  举报