[LeetCode]464. 我能赢吗

75 篇文章 0 订阅
1 篇文章 0 订阅

464. 我能赢吗

题目

464. 我能赢吗
在 "100 game" 这个游戏中,两名玩家轮流选择从 110 的任意整数,累计整数和,先使得累计整数和 达到或超过  100 的玩家,即为胜者。

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

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

给定两个整数 maxChoosableInteger (整数池中可选择的最大数)和 desiredTotal(累计和),若先出手的玩家是否能稳赢则返回 true ,否则返回 false 。假设两位玩家游戏时都表现 最佳 。

 

示例 1:

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

输入:maxChoosableInteger = 10, desiredTotal = 0
输出:true
示例 3:

输入:maxChoosableInteger = 10, desiredTotal = 1
输出:true
 

提示:

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

解法

方法0:朴素版回溯

TLE,这种回溯的思路是值得借鉴的

     
        //TLE
        //41/57
        //case:
        //20
        //210
        boolean[] vis;
        int maxChoosableInteger, desiredTotal;

        public boolean canIWin(int maxChoosableInteger, int desiredTotal) {
            this.maxChoosableInteger = maxChoosableInteger;
            this.desiredTotal = desiredTotal;
            for (int i = 1; i <= maxChoosableInteger; i++) {
                vis = new boolean[21];
                vis[i] = true;
                if (dfs(i)) return true;
            }
            return false;
        }


        /**
         * @param curSum 当前已经选的数的总和
         * @return
         */
        private boolean dfs(int curSum) {
            //已经达到目标数desiredTotal,返回true
            if (curSum >= desiredTotal) return true;
            //遍历从1 到 maxChoosableInteger的每一个数
            for (int i = 1; i <= maxChoosableInteger; i++) {
                if (vis[i]) continue;//被访问过,不需要继续
                vis[i] = true;//标记
                //如果curSum+i 即对手能赢得游戏,意味着当前玩家失败
                if (dfs(curSum + i)) {
                    return vis[i] = false;
                }
                vis[i] = false;//恢复
            }
            return true;
        }

方法1:回溯+状态压缩

  • 1 <= maxChoosableInteger <= 20数据范围,想到可能需要状态压缩
       Boolean[] dp;
        int maxChoosableInteger;

        public boolean canIWin(int maxChoosableInteger, int desiredTotal) {
            //最大的可以选的数(maxChoosableInteger)大于等于最终目标数(desiredTotal),A先抽,直接拿desiredTotal,胜出
            if (desiredTotal <= maxChoosableInteger) return true;
            //[1...maxChoosableInteger]之间的总数的和小于最终目标数(desiredTotal),A和By将数选完,也不能凑成(desiredTotal),A赢不了,返回false
            if ((1 + maxChoosableInteger) * maxChoosableInteger / 2 < desiredTotal) return false;
            this.maxChoosableInteger = maxChoosableInteger;
            dp = new Boolean[1 << maxChoosableInteger];
            return dfs(desiredTotal, 0);
        }

        /**
         * @param desiredTotal 当前离达成目标的原始desiredTotal还差多少
         * @param state        当前从1到maxChoosableInteger之间的每个数被选中的状态,在二进制下,1为选中,0为未选中
         * @return
         */
        private boolean dfs(int desiredTotal, int state) {
            //如果该state被标记过,返回
            if (dp[state] != null) return dp[state];
            //从 1 到 maxChoosableInteger 判断每一位是否被选中过
            for (int i = 1; i <= maxChoosableInteger; i++) {
                //当前位 需要判断下当前位是否是1(二进制下)
                int cur = 1 << (i - 1);
                if ((cur & state) == 0) {//当前位不是1是0,表示当前位可以选
                    //case1: 选中当前的数i后,可以达成最终的目标
                    //cas2:  选中当前的数i后,暂时不能达成目标,但是对手最终不能达成目标,意味着当前玩家最终可以达成目标
                    //cur | state 表示将当前数i选中 送到下一轮的状态中
                    if (desiredTotal - i <= 0 || !dfs(desiredTotal - i, cur | state)) {
                        return dp[state] = true;
                    }
                }
            }
            return dp[state] = false;
        }

方法2:回溯+HashMap

国际站的高赞的解法,链接

Map<Integer, Boolean> memo = new HashMap();
boolean[] vis;

public boolean canIWin(int maxChoosableInteger, int desiredTotal) {
    //最大的可以选的数(maxChoosableInteger)大于等于最终目标数(desiredTotal),A先抽,直接拿desiredTotal,胜出
    if (desiredTotal <= maxChoosableInteger) return true;
    //[1...maxChoosableInteger]之间的总数的和小于最终目标数(desiredTotal),A和By将数选完,也不能凑成(desiredTotal),A赢不了,返回false
    if ((1 + maxChoosableInteger) * maxChoosableInteger / 2 < desiredTotal) return false;
    vis = new boolean[maxChoosableInteger + 1];
    return helper(desiredTotal);
}

public boolean helper(int desiredTotal) {
    if (desiredTotal <= 0) return false;
    int key = format(vis);
    if (!memo.containsKey(key)) {
        //遍历vis中的数,尝试没有被选中的
        for (int i = 1; i < vis.length; i++) {
            if (!vis[i]) {//i没有被选择
                vis[i] = true;//标记
                // 当前i被选中,留给对手的desiredTotal - i的空间,但是对手没法胜出
                if (!helper(desiredTotal - i)) {
                    memo.put(key, true);
                    vis[i] = false;//恢复
                    return true;
                }
                vis[i] = false;//恢复
            }
        }
        memo.put(key, false);
    }
    return memo.get(key);
}

// 遍历vis数组,每一位true的设置为1,相当于压缩
public int format(boolean[] vis) {
    int res = 0;
    for (boolean b : vis) {
        res <<= 1;
        if (b) res |= 1;
    }
    return res;
}
  • 另,使用数组做key

TLE

        int[] state;
        Map<String, Boolean> memo = new HashMap<>();

        public boolean canIWin(int maxChoosableInteger, int desiredTotal) {
            if (desiredTotal <= maxChoosableInteger) return true;
            if ((1 + maxChoosableInteger) * maxChoosableInteger / 2 < desiredTotal) return false;
            state = new int[maxChoosableInteger + 1];
            return dfs(desiredTotal);
        }

        private boolean dfs(int desiredTotal) {
            String key = Arrays.toString(state);
            if (memo.containsKey(key)) return memo.get(key);
//            System.out.printf("key:%s ", key);
            for (int i = 1; i < state.length; i++) {
                if (state[i] == 0) {
                    state[i] = 1;
//                    System.out.printf(" %d\n", desiredTotal);
                    if (desiredTotal - i <= 0 || !dfs(desiredTotal - i)) {
                        state[i] = 0;
                        memo.put(key, true);
                        return true;
                    }
                    state[i] = 0;
                }
            }
            memo.put(key, false);
            return false;
        }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值