leetcode464我能赢吗

leetcode464 Can I Win

Posted by BY on May 25, 2019

前言

新的一年,好好学习。

正文

问题来源

本问题来自leetcode上的464题。

问题描述

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

示例 1:

输入:
maxChoosableInteger = 10
desiredTotal = 11

输出:
false

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

分析:

我的想法是,采用回溯法。相同的一层是自身计算过程,则下一层则代表对手的计算过程。只要能将对手计算逼迫到输,则返回成功否则返回失败;下一层,也就是对手环节也会将自身的对手逼迫到输,若能则返回成功否则返回失败。
如下是我写的代码,然而超出时间限制。

func canIWin(maxChoosableInteger int, desiredTotal int) bool {
    if desiredTotal <= maxChoosableInteger {
        return true
    }
    if desiredTotal * 2 > (1 + maxChoosableInteger) * maxChoosableInteger {
        return false
    }
   return canWin(maxChoosableInteger, desiredTotal, []int{})
}

func exist(a int, num []int) bool {
    for i := 0; i < len(num); i++ {
        if a == num[i] {
            return true
        }
    }
    return false
}

func canWin(maxChoosableInteger int, desiredTotal int, record []int ) bool {
    if desiredTotal <= 0 {
        return false
    }
    for i := 1; i <= maxChoosableInteger; i++ {
        if exist(i, record) {
            continue
        }
        flag := canWin(maxChoosableInteger, desiredTotal-i, append(record, i))
        if flag == false {
            return true
        }
    }
    return false
}

然后参考了一下柳婼的代码
分析:两个特殊情况:1.如果能够选的最大数字大于等于desiredTotal,第一个人又不傻肯定选最大的值,那样他就赢即:
if(maxn >= desiredTotal) return true;
2.如果所有能够选的数字的总和都小于desiredTotal,再怎么选两个人都不可能赢,所以肯定输:
总和就是首项加末项的和乘以项数除以2:if((1 + maxn) * maxn / 2 < desiredTotal) return false;
然后建立一个canWin函数,需要用visited标记某个数字是否被选过。因为可选的数字最大不超过20,则可以用一个整型数组标记,因为整型有32位,如果1被选过就把第1位(不是第0位,当然也可以从0开始)标记为1,如果2被选过就把第2位标记为1,这样保证所有数字不重复
所以传入两个值,一个是想要达到(或者说超过,也就是大于等于啦)的目标数字target,另一个是visited数字标记哪些数字被选过了
用map标记当前情况在map表中是否存在,存在的话结果保存在map里面,如果我们发现这个visited也就是这个数字选择的情况已经被保存过了,就直接返回在map里面保存的结果
遍历i从1到maxn(maxn是可以选择的最大值,即maxChoosableInteger),表示考虑选择i的情况,用mask = 1 « i,如果mask和visited进行与运算,如果等于0说明当前的visited没有被访问过,就可以考虑这个i的情况,如果要选的这个i大于target,不傻的这个人就会选择i因为肯定能赢,还有种情况自己能赢,就是对方输了,即:canWin(target - i, mask | visited) == false,(mask | visited表示把i那位也标记为1)这个时候把visited情况存起来并且return true,表示赢了,如果所有数字都遍历完还是没有return true,那就最后return false,return false之前不要忘记把当前状态存储起来,也就是m[visited] = false;

class Solution {
private:
    int maxn;
    map<int, bool> m;
public:
    bool canIWin(int maxChoosableInteger, int desiredTotal) {
        maxn = maxChoosableInteger;
        if(maxn >= desiredTotal) return true;
        if((1 + maxn) * maxn / 2 < desiredTotal) return false;
        return canWin(desiredTotal, 0);
    }
    bool canWin(int target, int visited) {
        if(m.find(visited) != m.end()) return m[visited];
        for(int i = 1; i <= maxn; i++) {
            int mask = (1 << i);
            if((mask & visited) == 0 && (i >= target || canWin(target - i, mask | visited) == false)) {
                m[visited] = true;
                return true;
            }
        }
        m[visited] = false;
        return false;
    }
};

其实上面思想是相通的,多了两个精妙的优化。
1 采用int类型来存访问过的(题干中maxChoosableInteger不超过20) 2 map中存计算过结果

var m map[int]bool

func canIWin(maxChoosableInteger int, desiredTotal int) bool {
    if desiredTotal <= maxChoosableInteger {
        return true
    }
    if desiredTotal * 2 > (1 + maxChoosableInteger) * maxChoosableInteger {
        return false
    }
    m = make(map[int]bool)
   return canWin(maxChoosableInteger, desiredTotal, 0)
}

func canWin(maxChoosableInteger int, desiredTotal int, record int) bool {
    if desiredTotal <= 0 {
        m[record] = false
        return false
    }
    if ret, ok := m[record]; ok {
        return ret
    }
    for i := 1; i <= maxChoosableInteger; i++ {
        mask := 1 << uint(i)
        if (mask & record) != 0 {
            continue
        }
        flag := canWin(maxChoosableInteger, desiredTotal-i, mask|record)
        if flag == false {
            m[record] = true
            return true
        }
    }
    m[record] = false
    return false
}

总结:

勤思考。

结语

不管怎么样好好加油。