当前位置: 首页 > news >正文

【记忆化搜索】猜数字游戏Ⅱ

文章目录

  • 375. 猜数字大小 II
  • 解题思路:暴搜 -> 记忆化搜索

在这里插入图片描述

375. 猜数字大小 II

375. 猜数字大小 II

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

  1. 我从 1n 之间选择一个数字。
  2. 你来猜我选了哪个数字。
  3. 如果你猜到正确的数字,就会 赢得游戏
  4. 如果你猜错了,那么我会告诉你,我选的数字比你的 更大或者更小 ,并且你需要继续猜数。
  5. 每当你猜了数字 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

解题思路:暴搜 -> 记忆化搜索

​ 首先这道题先搞清楚题意很重要,虽然我们很习惯性的会去想二分的猜数字,但是在这道题二分不是最优解,因为还得结合节点的值,二分只是确保平均的查找路径是最短的,但是不能确保拿到的是最小现金数,也就是说,这道题其实就是一个暴搜!

​ 但是因为递归树太麻烦了这道题,有太多的路径,所以下面我们将其抽象一下,就得到下图:

​ 其中左右子树是有很多路径的,该图只是简化了,在左右子树还得继续暴力枚举对应区间上的路径可能!

​ 那么既然是暴搜,我们就 dfs() 函数帮我们拿到能够确保获胜的最小现金数,因为涉及到区间分割,所以要传两个参数 leftright 作为区间的左右边界控制一下,然后返回值就是整型,不过很容易想到!

​ 此时当我们选了 i 元素之后,那么对于 i 元素来说,它的左右子树拿到的就是各自能获胜的最小现金数,此时细节来了,对于 i 整棵子树来说,此时的最小现金数应该是左右子树各自的最小现金数中的最大值

​ 这是因为左右子树拿到的最小现金数是保证左右子树各自能获胜而使用的最小现金数,但是对于 i 元素来说,是要看整体的,无论是走左边还是右边都必须要赢,所以就必须选择大的那个才符合要求,如下图所示:

在这里插入图片描述

​ 代码如下所示,最好结合代码和上面的解释一起理解:

class Solution {
public:
    int getMoneyAmount(int n) {
        return dfs(1, n);
    }

    int dfs(int left, int right)
    {
        // 函数递归出口(left==right的时候说明肯定是找到了,此时也是返回0,不需要支付金额)
        if(left >= right) 
            return 0;
        
        int ret = INT_MAX; // 记录最小现金数
        for(int i = left; i <= right; ++i) // 枚举区间所有元素的路径
        {
            int x = dfs(left, i - 1);
            int y = dfs(i + 1, right);
            ret = min(ret, i + max(x, y)); // 记得左右子树的最大值要加上i才算当前的现金数,然后再更新最小现金数
        }
        return ret;
    }
};

​ 虽然思路正确,但是这道题暴搜仍然会超时!

​ 很明显,因为是暴搜,不同子树之间存在大量重复的区间,所以我们可以用记忆化搜索优化,将这些出现过的区间的最小现金数记录到备忘录中,下次进入函数前先判断备忘录是否有记录该区间的最小现金数,有的话直接返回即可,大大的提高了效率!

​ 另外初始化备忘录的问题,其实我们 直接初始化为 0 即可,因为出现结果为 0 的情况只有在函数递归出口,所以我们可以将备忘录的判断放到函数递归出口的下面进行判断即可,省去了我们去初始化的操作!

class Solution {
private:
    int memory[201][201]; // 二维数组作为备忘录,元素值表示[i, j]区间的最小现金数
public:
    int getMoneyAmount(int n) {
        return dfs(1, n);
    }

    int dfs(int left, int right)
    {
        // 函数递归出口
        if(left >= right) 
            return 0;
            
        // 进入函数前先判断备忘录是否有记录该区间的最小现金数,有的话直接返回
        if(memory[left][right] != 0)
            return memory[left][right];
        
        int ret = INT_MAX; // 记录最小现金数
        for(int i = left; i <= right; ++i)
        {
            int x = dfs(left, i - 1);
            int y = dfs(i + 1, right);
            ret = min(ret, i + max(x, y)); // 记得左右子树的最大值要加上当前的i才算当前的现金数,然后再更新最小现金数
        }

        // 出函数之前,将该区间的结果记录下来
        memory[left][right] = ret;
        return ret;
    }
};

在这里插入图片描述

相关文章:

  • 2025年02月14日Github流行趋势
  • TensorFlow 实现任意风格的快速风格转换
  • cs106x-lecture9(Autumn 2017)-SPL实现
  • PLC的集成RAM,存储器卡,用户程序存储空间,数据存储容量分别指的什么,有什么关联?
  • set的使用(c++)
  • JVM基础---java类加载机制(类的生命周期,类加载器,双亲委派模型)
  • XSS攻击(跨站脚本攻击)详解与实战
  • 零基础入门机器学习 -- 第五章决策树与随机森林
  • BSD协议栈:多播
  • Visual Basic语言的数据类型
  • Logo语言的图形用户界面
  • jar命令解压jar包及更新jar的配置文件
  • RTMP(Real-Time Messaging Protocol)
  • 网工项目理论1.11 网络出口设计
  • seata基本使用
  • 【Java】Mongodb
  • UI自动化教程 —— 元素定位技巧:精确找到你需要的页面元素
  • Kafka偏移量管理全攻略:从基础概念到高级操作实战
  • 如何在yolov8系列运行自己的数据集
  • NAT(网络地址转换)技术详解:网络安全渗透测试中的关键应用与防御策略
  • 甘肃白银煤矿透水事故最新进展:3名被困矿工已无生命体征
  • 张永宁任福建宁德市委书记
  • 九江银行落地首单畜牧业转型金融业务,助推传统农业绿色智能
  • 三件珍贵标本开箱!中国恐龙大展5月26日在沪开幕,明星标本汇聚一堂
  • 玉林一河段出现十年最大洪水,一村民被冲走遇难
  • 习近平向第三十四届阿拉伯国家联盟首脑理事会会议致贺信