【LeetCode】464. 我能赢吗


原题链接: 464. 我能赢吗
很少做博弈论的题目,看了题解后很受启发,写了点总结。

题目大意

在 “100 game” 这个游戏中,两名玩家轮流选择从 1 到 10 的任意整数,累计整数和,先使得累计整数和 达到或超过 100 的玩家,即为胜者。
如果我们将游戏规则改为 “玩家 不能 重复使用整数” 呢?
例如,两个玩家可以轮流从公共整数池中抽取从 1 到 15 的整数(不放回),直到累计整数和 >= 100。
给定两个整数 maxChoosableInteger (整数池中可选择的最大数)和 desiredTotal(累计和),若先出手的玩家是否能稳赢则返回 true ,否则返回 false 。假设两位玩家游戏时都表现 最佳 。
示例:

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

数据范围:

1 <= maxChoosableInteger <= 20
0 <= desiredTotal <= 300

思路

本题可用记忆化搜索来递归,每一次递归时的状态为当前已经选择的整数池,该整数池可用unordered_set来存储,但在递归过程中需要多次复制,复杂度较高。考虑到数据范围为1~20,而一个int占用32位,因此可以用一个整数x来表示状态,即当前已选整数池。在状态转移中,可借助位运算。

具体地,整数x的低20位可表示当前状态,从右往左为第0-19位,可以表示 2 20 2^{20} 220 个状态,可用f数组来存储各个状态是否必胜。初始时都不选,则x为0;当选择某个数i后,则x的右边第i-1位置为1。

对于该博弈问题,某人是否必胜取决于当前的棋局,也即x的大小,通过棋局下一步的状态是否必胜可得到当前棋局是否必胜:从未选的整数池中选择某个数i转移到下一状态,若在下一状态下能够必败,则当前棋局必胜;若遍历未选的整数池,任意下一状态下都必胜,则当前棋局必败。递归的边界条件为求和大于等于desiredTotal :sum + i + 1 >= m。

其中,f表示某个x状态下是否必胜,初始为-1,必胜标记为1,必败标记为0.用来减小重复运算,例如先选1再选2,与先选2再选1,其状态一致,而某时刻能否必胜取决于棋局状态,因此查f,若已经计算过该状态下的胜负情况则可直接返回。

代码

class Solution {
public:
    vector<int> f;
    int n, m;

    int dp(int x) {
        // 该状态x已经搜索过,确认过能否必胜
        if (f[x] != -1) return f[x]; 
        int sum = 0; // 求和该状态下的总和sum
        for (int i = 0; i < n; i ++ )
            if (x >> i & 1)
                sum += i + 1;
        for (int i = 0; i < n; i ++ ) { // 遍历未选的整数池
            if (x >> i & 1) continue; 
            if (sum + i + 1 >= m) return f[x] = 1; // 边界条件
        // 博弈论中的状态转移,若存在某个下一状态必败,则当前状态必胜
            if (!dp(x + (1 << i))) return f[x] = 1; 
        }
        // 任意下一状态都必胜,则该状态必败
        return f[x] = 0;
    }

    bool canIWin(int _n, int _m) {
        n = _n, m = _m;
        if (n * (n + 1) / 2 < m) return false; // 特判
        f.resize(1 << n, -1); // 开2^20的数组来存储某棋局状态x下,是否必胜
        return dp(0); // 先手面对的状态为0,即未选任何数,x为0
    }
};

复杂度

时间复杂度:需要搜索 2 20 2^{20} 220种状态,每次都需要遍历未选的整数池,因此时间复杂度为 O ( 20 ∗ 2 20 ) O(20*2^{20}) O(20220)
空间复杂度:开了数组f来存储当前状态x的胜负情况, O ( 2 20 ) O(2^{20}) O(220)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值