464-我能赢吗-又是一道愁人的状压DP问题

题目

核心思路

这道题思考了挺长的时间,却始终没有想到解决方案,主要是题目中两位玩家游戏时都表现最佳感觉表示不出来,看了这位大佬的深搜之后豁然开朗。
我之前主要的疑惑是怎么只用一个搜索函数就同时表示出两个玩家的游戏胜负,因为这一轮是我在选数,返回的结果也是我是否能取胜;而下一轮就是对手选数及能否取胜了(其实现在想来,传参是传入一个标识位也是可行的)。在深搜代码中,还是有部分挺值得思考的。

  public boolean dfs(int desiredTotal, boolean[] visited){
        if(desiredTotal <= 0) return false;
        for(int i = 1; i < visited.length; i++){
            if(!visited[i]){
                visited[i] = true;
                boolean tmp = dfs(desiredTotal - i, visited);
                visited[i] = false;
                if(!tmp) return true;
            }
        }
        return false;
    }

代码中的 visited数组 表示的是1-max是否被取过了。而代码的主要部分

        if(!visited[i]){
            visited[i] = true;
            boolean tmp = dfs(desiredTotal - i, visited);
            visited[i] = false;
            if(!tmp) return true;
        }

并不是记录先手的人赢还是后手的人赢,而是返回当前轮游戏中,游戏人(先手的或者后手的)是否能稳赢,这样本轮游戏人(以下以 代称)又可以得到下一轮游戏人(以下以 对手 代称)的游戏结果dfs(desiredTotal - i, visited)。这里有两种情况, 取了 i 这个整数后,要么 对手 可以稳赢,要么 对手 不能稳赢。而其中 对手 不能稳赢也就意味着在他那一轮游戏中出现了 可以稳赢的情况。
这里要先回溯在返回,因为 并不是特指先手玩家或者后手玩家, 只是本轮的游戏玩家,而且 要做出最优的游戏选择所以 就要在所有剩余的取数情况中找到 对手 可以稳输的情况,这样 就稳赢了,所以有 if(!tmp) return true; 这样一行代码,对手稳输情况返回true,而 的返回值又会交给上一轮的 对手 来考虑策略,这样就可以保证双方都能做出最优的选择,而不会某一轮游戏中一名玩家找到最优解就直接一路return到第一轮游戏。

状压DP

通过上边的深搜,其实不难发现:如果在某次游戏中的某轮游戏中,前面取了[1,2,3]和[2,1,3]对本轮游戏是否稳赢没有影响,因为他们的和都为6,也就是下一轮都要执行 dfs(desiredTotal - 6 - i, visited),这里的 i 和上边一样用来遍历没取过的数 i。也就是说对于同一个已经取走数的集合([1,2,3]与[2,1,3]),由于剩下的desiredTotal也是相同的,故dfs方法给出的结果也是相同。这样就有了很多重复计算的过程,考虑使用一个memo数组记录也就理所应当了。
因为对于每一个数只有取了或者没取两种状态,而且对于同一个状态,取的顺序并不影响结果。所以可以想到使用状态压缩DP来记录每一种状态。

完整代码

剩下的就是很典型的状压DP计算方法了。

class Solution {

    Boolean[] memo = new Boolean[1 << 20];

    public boolean canIWin(int maxChoosableInteger, int desiredTotal) {
        //先取稳赢
        if(maxChoosableInteger >= desiredTotal) return true;
        //两人都不能赢
        if(((1 + maxChoosableInteger) * maxChoosableInteger / 2) < desiredTotal) return false;
        
        return dfs(maxChoosableInteger, desiredTotal, 0);
    }

    public boolean dfs(int max, int desiredTotal, int state){
        if(memo[state] != null) return memo[state];
        
        for(int i = 0; i < max; i++){
            int tmp = 1 << i;
            if((tmp & state) == 0){
                //我这轮稳赢或者对面下一轮稳输
                if(desiredTotal <= i + 1 || !dfs(max, desiredTotal - i - 1, state | tmp)){
                    memo[state] = true;
                    return true;
                }
            }
        }
        memo[state] = false;
        return false;
    }
}

对于位运算部分:int tmp = 1 << i; temp & state,通过tmp取到当前状态state是否取到了i + 1这个数,我遍历从零开始主要是移位的时候舒服一点。state | tmp 就是在 state 的基础上取 i + 1 这个数。

总结

虽然方法叫状态压缩DP,其实只是一种枚举的方式,所以主要还是要看数据范围是否能承受O(2^N)的时间复杂度,对于数据范围小于等于30 的问题还是比较好用的,剩下最重要的就是要理解题目的意思了。
如果有写的不正确的地方还请指出,感恩相遇~

?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,992评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,212评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,535评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,197评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,310评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,383评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,409评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,191评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,621评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,910评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,084评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,763评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,403评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,083评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,318评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,946评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,967评论 2 351