leetcode464- Can I Win- medium

In the "100 game," two players take turns adding, to a running total, any integer from 1..10. The player who first causes the running total to reach or exceed 100 wins.

What if we change the game so that players cannot re-use integers?

For example, two players might take turns drawing from a common pool of numbers of 1..15 without replacement until they reach a total >= 100.

Given an integer maxChoosableInteger and another integer desiredTotal, determine if the first player to move can force a win, assuming both players play optimally.

You can always assume that maxChoosableInteger will not be larger than 20 and desiredTotal will not be larger than 300.

Example

Input:
maxChoosableInteger = 10
desiredTotal = 11

Output:
false

Explanation:
No matter which integer the first player choose, the first player will lose.
The first player can choose an integer from 1 up to 10.
If the first player choose 1, the second player can only choose integers from 2 up to 10.
The second player will win by choosing 10 and get a total = 11, which is >= desiredTotal.
Same with other integers chosen by the first player, the second player will always win.

算法:

1.和前面的取石头游戏很像,仍可以从前面的状态转移方程得到可以借鉴的地方,就是我方赢的方法还是,试探取不同的可行的石子个数,如果任意一次有成功将对方置入必输状态,即可了。不过加入不能取重复的的限制瞬间变难,破解方法是DFS,去遍历每种取的顺序的可能。用一个boolean[]数组记录使用过的数字,遍历每种可能,暴力破解。

2.优化时间复杂度。单纯DFS会TLE,因为递归到底层开销很大,而且有很多相同的子状态被重复计算了。所以我们应该中途记录下某个状态对应的boolean结果,如果遍历到某个状态发现记录过直接返回结果即可。

问题:怎么定义一个状态?一开始以为要1.已用过的数字情况 2. total合在一起才是一个状态,后来发现单独一个1就足够了,因为在一个特定的问题中,maxI总是固定的,那如果你1固定了,maxI - 所有用过的数字就是2了,那2也固定了。所以2是包含在1里的。所以最后只要用一个Map<用过数字的情况,Boolean>来做memo即可。

问题:怎么表示用过数字的情况呢,这里又是一个优化,并不用boolean[] isUsed来表示,这样map要索引到的话每次要开一个新的boolean[],空间消耗。本题给了限制可使用数字不会超过20,利用它你可以使用int来取代boolean[]。也就是把integer上的每一位0还是1来当成boolean的结果。如TFFT用1001来表示。标记某一数用过没用过就转为位运算了。isUsed = isUsed ^ (1 << (数-1) )。一个位异或上1就是进行反转。

细节:

1. 这种DFS改变状态的标记isUsed每次改变一定要成对出现,改过,递归,改回来。本题也是要记住在for循环头尾要改,中途直接return要改(只是本题凑巧用int基本数据类型记录状态,如果是return的话本身就不被保留,可以不用改回去了)。

2. 判断胜利有两个条件,一个是这一步就赢了(可以取的数比界线高),一个是将来我肯定会赢了(我这几种可取的方法里有一种会让对手陷入必败之地)。不要漏了第一种。

3. 放入memo的是你调用这个函数的时候最开始的isUsed状态,而不是你改动后的,千万小心!

4. 位运算的时候尽量还是用括号括一下保证运算顺序,有几个优先级真的很难记,保险一点。

5. 边界条件有意思,一个是如果一开始可取的数就大于边界了,稳赢;一个是如果你们两个把所有数字都取完了都还没到边界,游戏没意义,你们都输

1.我的实现

class Solution {
    public boolean canIWin(int maxChoosableInteger, int desiredTotal) {

        // 边界条件有意思
        if (maxChoosableInteger >= desiredTotal) {
            return true;
        }
        if ((1 + maxChoosableInteger) * maxChoosableInteger / 2 < desiredTotal) {
            return false;
        }
        Map<Integer, Boolean> memo = new HashMap<>();
        return helper(maxChoosableInteger, desiredTotal, 0, memo);
    }

    private boolean helper(int maxI, int total, int isUsed, Map<Integer, Boolean> memo) {

        if (memo.containsKey(isUsed)) {
            return memo.get(isUsed);
        }

        for (int i = 1; i <= maxI; i++) {
            //一定要把前面的计算结果扩起来,==优先级比&高
            if (((isUsed >> (i - 1)) & 1) == 1) {
                continue;
            }
            // 某一位异或1就是反转这一位!这里反转就是改变标记用了没有的状态。
            isUsed = isUsed ^ (1 << (i - 1));
            // 注意 i >= total的时候已经可以判决是胜利了,其实相当于把出口化解在了这里。
            if (i >= total || !helper(maxI, total - i, isUsed, memo)) {
                // 这里返回前按理说也要把使用状况改回来,但只不过java正好传引用参,回去的话本来就不会保留,所以就省略了。
                // 这里千万注意memo里放的是你没改过的状态!!不是你改后的!
                isUsed = isUsed ^ (1 << (i - 1));
                memo.put(isUsed, true);
                return true;
            }
            // 这里却还一定要记得把改变的状态恢复回来,因为for循环是函数内部,共享该变量,所以要改回来不能影响下一个变量。
            isUsed = isUsed ^ (1 << (i - 1));
        }

        memo.put(isUsed, false);
        return false;
    }

}

2. 我的实现去掉注释方便阅读版

class Solution {
    public boolean canIWin(int maxChoosableInteger, int desiredTotal) {

        if (maxChoosableInteger >= desiredTotal) {
            return true;
        }
        if ((1 + maxChoosableInteger) * maxChoosableInteger / 2 < desiredTotal) {
            return false;
        }
        Map<Integer, Boolean> memo = new HashMap<>();
        return helper(maxChoosableInteger, desiredTotal, 0, memo);
    }

    private boolean helper(int maxI, int total, int isUsed, Map<Integer, Boolean> memo) {

        if (memo.containsKey(isUsed)) {
            return memo.get(isUsed);
        }

        int temp = isUsed;
        for (int i = 1; i <= maxI; i++) {
            if (((isUsed >> (i - 1)) & 1) == 1) {
                continue;
            }
            isUsed = isUsed ^ (1 << (i - 1));
            if (i >= total || !helper(maxI, total - i, isUsed, memo)) {
                memo.put(temp, true);
                return true;
            }
            isUsed = isUsed ^ (1 << (i - 1));
        }
        memo.put(temp, false);
        return false;
    }
}

3.九章实现版

/**
* 本参考程序来自九章算法,由 @九章算法 提供。版权所有,转发请注明出处。
* - 九章算法致力于帮助更多中国人找到好的工作,教师团队均来自硅谷和国内的一线大公司在职工程师。
* - 现有的面试培训课程包括:九章算法班,系统设计班,算法强化班,Java入门与基础算法班,Android 项目实战班,
* - Big Data 项目实战班,算法面试高频题班, 动态规划专题班
* - 更多详情请见官方网站:http://www.jiuzhang.com/?source=code
*/ 

public class Solution {
    int[] dp;
    boolean[] used;
    public boolean canIWin(int maxChoosableInteger, int desiredTotal) {
        int sum = (1 + maxChoosableInteger) * maxChoosableInteger / 2;
        if(sum < desiredTotal) {        //取完所有数,也达不到desiredTotal,无法赢得游戏
            return false;
        }
        if(desiredTotal <= maxChoosableInteger) {      //第一步就可以获得胜利
            return true;
        }
        dp = new int[1 << maxChoosableInteger];
        Arrays.fill(dp , -1);
        used = new boolean[maxChoosableInteger + 1];

        return helper(desiredTotal);
    }

    public boolean helper(int desiredTotal){
        if(desiredTotal <= 0) {
            return false;
        }
        int key = format(used);         //把used数组转为十进制表示
        if(dp[key] == -1){
            for(int i = 1; i < used.length; i++){    //枚举未选择的数
                if(!used[i]){
                    used[i] = true;

                    if(!helper(desiredTotal - i)){
                        dp[key] = 1;
                        used[i] = false;
                        return true;
                    }
                    used[i] = false;
                }
            }
            dp[key] = 0;
        }
        return dp[key] == 1;
    }

    public int format(boolean[] used){
        int num = 0;
        for(boolean b: used){
            num <<= 1;
            if(b) {
                num |= 1;
            }
        }
        return num;
    }
}
时间: 2024-11-02 22:54:30

leetcode464- Can I Win- medium的相关文章

LeetCode 464 - Can I Win - Medium (Python)

In the "100 game," two players take turns adding, to a running total, any integer from 1..10. The player who first causes the running total to reach or exceed 100 wins. What if we change the game so that players cannot re-use integers? For examp

LeetCode Problems List 题目汇总

No. Title Level Rate 1 Two Sum Medium 17.70% 2 Add Two Numbers Medium 21.10% 3 Longest Substring Without Repeating Characters Medium 20.60% 4 Median of Two Sorted Arrays Hard 17.40% 5 Longest Palindromic Substring Medium 20.70% 6 ZigZag Conversion Ea

[Swift]LeetCode464. 我能赢吗 | Can I Win

In the "100 game," two players take turns adding, to a running total, any integer from 1..10. The player who first causes the running total to reach or exceed 100 wins. What if we change the game so that players cannot re-use integers? For examp

关于win下Memcached安装步骤

2天对我来说有点煎熬..数据量达到17w的时候 我本地执行查询速度特别慢! 请教了一些php大牛如何解决速度问题,在加了索引和优化sql后还是速度慢!我决定在win环境下用Memcached和memcache 来处理,先声明一下: memcache是php的拓展,memcached是客户端,复杂的说:Memcache模块提供了于memcached方便的面向过程及面向对象的接口,memcached是为了降低动态web应用 从数据库加载数据而产生的一种常驻进程缓存产品. 因为我本地用的是xampp集

Csharp: speech to text, text to speech in win

? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86

[LeetCode] 011. Container With Most Water (Medium) (C++/Java/Python)

索引:[LeetCode] Leetcode 题解索引 (C++/Java/Python/Sql) Github: https://github.com/illuz/leetcode 011.Container_With_Most_Water (Medium) 链接: 题目:https://oj.leetcode.com/problems/container-with-most-water/ 代码(github):https://github.com/illuz/leetcode 题意: 给一些

[LeetCode] 012. Integer to Roman (Medium) (C++/Java/Python)

索引:[LeetCode] Leetcode 题解索引 (C++/Java/Python/Sql) Github: https://github.com/illuz/leetcode 012.Integer_to_Roman (Medium) 链接: 题目:https://oj.leetcode.com/problems/integer-to-roman/ 代码(github):https://github.com/illuz/leetcode 题意: 把十进制转为罗马数. 分析: 模拟即可.

win 8 远程桌面文件复制问题(图)

用win7连接远程桌面,可以很方便的在宿主机之间文件复制粘贴.但用win8.1远程连接桌面时,却发现不能复制文件了.查看网上资料,最后总结实现此过程如下: win+R,运行mstsc,如下图所示: 点击ok,然后选择show options,如下图所示: 切换到选项卡local resources,然后点击块local devices and resources中的more,如下图所示: 选择drives中要共享的磁盘,点击ok按钮,如下图所示: 最后点击connect按钮,即可连接. 转载请注

系统引导UEFI 引导,Win下挂载EFI分区教程

首先了解几个东西: 1. MBR分区表:Master Boot Record,即硬盘主引导记录分区表,只支持容量在 2.1TB 以下的硬盘,超过2.1TB的硬盘只能管理2.1TB,最多只支持4个主分区或三个主分区和一个扩展分区,扩展分区下可以有多个逻辑分区. 2. GPT分区表:GPT,全局唯一标识分区表(GUID Partition Table),与MBR最大4个分区表项的限制相比,GPT对分区数量没有限制,但Windows最大仅支持128个GPT分区,GPT可管理硬盘大小达到了18EB.只有

Win Server 2012 R2 WSUS 无法识别 Win Server 2016 & Win10的解决办法

各位好,今天给大家分享一个我遇到的问题. 相信微软在推出了 Windows Server 2016和Win10 操作系统之后,有很多同学都第一时间进行了安装和测试,想第一时间感受一下全新的系统平台带来的新体验. 但是不知道有没有同学发现,如果我们在域环境中搭建了 Server 2016或者Win10的机器,域内的 WSUS 服务器有可能是无法识别出来的,所以会直接导致补丁推送失败,从而 Server2016和Win10的终端根本无法享受到Windows更新服务. 那么怎么办呢? 今天我就来给大家