Leetcode464. 我能赢吗

本文深入探讨了LeetCode题目464——“我能赢吗?”的解题策略,详细讲解了如何运用递归法及状态压缩+递归+记忆化的动态规划方法解决博弈类问题。通过具体示例,解析了如何判断先手玩家在特定规则下是否能稳赢。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Leetcode464. 我能赢吗

博弈问题
递归+备忘录+压缩状态

题目

在 “100 game” 这个游戏中,两名玩家轮流选择从 1 到 10 的任意整数,累计整数和,先使得累计整数和达到 100 的玩家,即为胜者。

如果我们将游戏规则改为 “玩家不能重复使用整数” 呢?

例如,两个玩家可以轮流从公共整数池中抽取从 1 到 15 的整数(不放回),直到累计整数和 >= 100。

给定一个整数 maxChoosableInteger (整数池中可选择的最大数)和另一个整数 desiredTotal(累计和),判断先出手的玩家是否能稳赢(假设两位玩家游戏时都表现最佳)?

你可以假设 maxChoosableInteger 不会大于 20, desiredTotal 不会大于 300。

示例

输入:
maxChoosableInteger = 10
desiredTotal = 11

输出:
false

解释:
无论第一个玩家选择哪个整数,他都会失败。
第一个玩家可以选择从 1 到 10 的整数。
如果第一个玩家选择 1,那么第二个玩家只能选择从 2 到 10 的整数。
第二个玩家可以通过选择整数 10(那么累积和为 11 >= desiredTotal),从而取得胜利.
同样地,第一个玩家选择任意其他整数,第二个玩家都会赢。

法1:递归法

  • 这是一道博弈类问题:“稳赢”指的是,假设有两个人A, B,A先抽整数然后轮到B,再轮到A…,每次都根据要凑的和选择最有利于自己赢的数。
    首先要理解什么是稳赢:在AB都各自执行最优策略达到某个状态时,A存在一种走法,在走完后,无论B怎么走,A都能取胜,因此我们需要不断地递归,遍历A走一步后的各种情况,从而判断A能否获胜。
  • 传统的做法可能想标记A和B,即表示当前状态下,该轮到谁进行操作。但实际上并不需要进行标记,其实这就是这类问题的关键: 因为是两个人在博弈, 所以从当前状态转移到下一个状态时, 就体现了动作执行这的变化。如说当前状态是A, 因为是两个人在玩, 下一个状态就是B. 这里很关键, 希望大家好好思考一下。
  • 递归函数dfs的含义是,某人(与AB无关)先手操作到达当前状态,能否稳赢。
class Solution {
    boolean[] visited; //用来标记某个数是否被使用过
    int maxChoosableInteger, desiredTotal;
    // 当前这个状态下, 能否稳赢.
    bool dfs(int total_sum) {
        // 递归的边界
        if (total_sum >= desiredTotal) return true; //这种情况是稳赢的
        //递归遍历各种情况
        for (int i = 1; i <= maxChoosableInteger; ++i) {
            if (visited[i]) continue;
            vis[i] = true;
            if (dfs(total_sum + i)) {
                visited[i] = false;
                return false; // 如果在当前状况下,对手通过取i能够获得胜利,说明自己无法稳赢,返回false
            }
            visited[i] = false; //当前这轮循环完成,换源i标记
        }
        //当所有情况遍历完,dfs都是false的时候,可以退出循环,表示对手无论取哪个数都无法胜利,这说明自己可以稳赢,返回true。
        return true;
    }
    bool canIWin(int maxChoosableInteger_, int desiredTotal_) {
    	visited = new int[maxChoosableInteger+1];
        maxChoosableInteger = maxChoosableInteger_;
        desiredTotal = desiredTotal_;
        
        for (int i = 1; i <= maxChoosableInteger; ++i) {
            visited[i] = true; // A先手,从第i个数开始
            if (dfs(i)) return true; //表示判断在A先手达到状态i的情况下,能否稳赢。
        }
        return false;
    }
};

法二:状态压缩+递归+记忆化(动态规划)

在递归法中,进行了大量的重复计算,比如[a选3,b选2]、[a选2,b选3],因此我们需要对状态进行记忆,这里就用到了状态压缩
状态压缩指的是用位来表示所有的情况,在该题目中,如果maxChooseableInteger = 4,那么我们可以使用“0000”四位表示状态,“0100”表示3已被选,“0101”表示3和1已被选。

class Solution {
    public boolean canIWin(int maxChoosableInteger, int desiredTotal) {
        if(maxChoosableInteger > 20 || maxChoosableInteger < 0 || desiredTotal > 300 || desiredTotal <0){
            return false;
        }
        if((1+maxChoosableInteger)*maxChoosableInteger/2 < desiredTotal){
            return false;
        }
        Boolean[] dp = new Boolean[1<<maxChoosableInteger]; //要用Boolean初始化,这样每个元素才能为null
        return backtrack(maxChoosableInteger, desiredTotal, dp, 0);
    }

// 表示从状态state开始,先手能否获胜。
    public boolean backtrack(int maxChoosableInteger, int desiredTotal, Boolean[] dp, int state){
        if(dp[state] != null){ //无需重复计算
            return dp[state];
        }
        for(int i = 1; i <= maxChoosableInteger; i++){
            if((state & (1 << (i-1))) == 0){ //若第i个数未被使用过,查看选i能否获胜。
                if(desiredTotal - i <= 0 || backtrack(maxChoosableInteger, desiredTotal-i, dp, state | (1<<(i-1))) == false){ //当选i大于目标值,或者接下来对手无法获胜时,该状态先手能够稳赢。
                    dp[state] = true;
                    return true;
                }
            }
        }
        //否则,不能稳赢
        dp[state] = false;
        return false;
    }
}
### 如何在 VSCode 中安装和配置 LeetCode 插件以及 Node.js 运行环境 #### 安装 LeetCode 插件 在 VSCode 的扩展市场中搜索 `leetcode`,找到官方提供的插件并点击 **Install** 按钮进行安装[^1]。如果已经安装过该插件,则无需重复操作。 #### 下载与安装 Node.js 由于 LeetCode 插件依赖于 Node.js 环境,因此需要下载并安装 Node.js。访问官方网站 https://nodejs.org/en/ 并选择适合当前系统的版本(推荐使用 LTS 版本)。按照向导完成安装流程后,需确认 Node.js 是否成功安装到系统环境中[^2]。 可以通过命令行运行以下代码来验证: ```bash node -v npm -v ``` 上述命令应返回对应的 Node.js 和 npm 的版本号。如果没有正常返回版本信息,则可能未正确配置环境变量。 #### 解决环境路径问题 即使完成了 Node.js 的安装,仍可能出现类似 “LeetCode extension needs Node.js installed in environment path” 或者 “command ‘leetcode.toggleLeetCodeCn’ not found” 的错误提示[^3]。这通常是因为 VSCode 未能识别全局的 Node.js 路径或者本地安装的 nvm 默认版本未被正确加载[^4]。 解决方法如下: 1. 手动指定 Node.js 可执行文件的位置 在 VSCode 设置界面中输入关键词 `leetcode`,定位至选项 **Node Path**,将其值设为实际的 Node.js 安装目录下的 `node.exe` 文件位置。例如:`C:\Program Files\nodejs\node.exe`。 2. 使用 NVM 用户管理工具调整默认版本 如果通过 nvm 工具切换了不同的 Node.js 版本,请确保设置了默认使用的版本号。可通过以下指令实现: ```bash nvm alias default <version> ``` 重新启动 VSCode 后测试功能键是否恢复正常工作状态。 --- #### 配置常用刷题语言 最后一步是在 VSCode 设置面板中的 LeetCode 插件部分定义个人习惯采用的主要编程语言作为默认提交方式之一。这样可以减少频繁修改编码风格的时间成本。 --- ### 总结 综上所述,要在 VSCode 上顺利启用 LeetCode 插件及其关联服务,除了基本插件本身外还需额外准备支持性的后台框架——即 Node.js 应用程序引擎;同时针对特定场景下产生的兼容性障碍采取针对性措施加以修正即可达成目标[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值