leetcode-我能赢吗

130 阅读3分钟

「这是我参与2022首次更文挑战的第22天,活动详情查看:2022首次更文挑战」。

题目

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

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

思路

这题虽然难度只是中等,但是我感觉比一些难度为困难的题目更加困难一些。
我是看了别人评论和题解才做出来,参考题解:《464. 我能赢吗,带备忘录的递归》。

整体思路就跟题解的一样,我就不重复了,我再说一下我理解的一些关键点。

备忘录的定义

备忘录memo是一个Boolean,之所以用Boolean而不是boolean,因为需要区分true、false、空 这3种状态。备忘录的定义是,当前状态下,接下来先手的人是否可以稳赢,而不是整个游戏的先手是否可以稳赢,这样理解代码中的

!dfs(cur|state, desiredTotal - i, dp, maxChoosableInteger)

才能自然而然,因为选择了一个数字后,要在新的状态下先手输,才能得到当前状态下先手赢,我看评论里面很多没有理解题解提出疑问的同学,都是卡在这个点的。

状态的定义

一开始我自己使用状态是int[],然后再转化成String作为key,但是这样做在第57组数据,也就是最后一组数据TLE了。所以题解中使用int做状态,用二进制的每一位表示这个数是否被选,比较巧妙,然后判断这个数字是否使用用到了位运算,递归的时候把这一位置为已经使用也用了位运算,省时省力,以后在其他题目也可以学习这个思路。

Java版本代码

class Solution {
    public boolean canIWin(int maxChoosableInteger, int desiredTotal) {
        // 可选最大数值大于等于需要的数值,先手肯定可以赢
        if (maxChoosableInteger >= desiredTotal) {
            return true;
        }
        // 所有数字和都小于需要的数值,先手肯定输
        if (maxChoosableInteger * (maxChoosableInteger + 1) / 2 < desiredTotal) {
            return false;
        }
        return dfs464(0, desiredTotal, new Boolean[1<<maxChoosableInteger], maxChoosableInteger);
    }

    private static boolean dfs464(int status, int left, Boolean[] memo, int maxChoosableInteger) {
        if (memo[status] != null) {
            return memo[status];
        }
        for (int i = 0; i < maxChoosableInteger; i++) {
            int currentStatus = 1 << i;
            if ((status & currentStatus) > 0) {
                continue;
            }
            if (i+1 >= left || !dfs464(currentStatus|status, left-(i+1), memo, maxChoosableInteger)) {
                return memo[status] = true;
            }
        }
        return memo[status] = false;
    }
}