leetcode 464.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 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.

想法:

现在已经条件反射了,看见这种第一眼连暴力求解思路都没有的题,不是动态规划就是贪心算法。

好嘛,贪心:每次拿最大的数字然后。。。你以为这俩player都是白痴吗。

假如数字集合1-10,A为了赢,拿10,B拿9,好吧,输了吧?

所以显然是动态规划。

动态规划我们一般用一个状态来表示,一个数组来保存之前可能用到的状态。

这里第一次我犯了个错误,没有考虑数字“是否被使用了”这个关键要点。所以以为一维数组就可以解决。

所以,错的惨兮兮。。。

之后我想,这玩意是不是就像走迷宫似的,为了尝试所有可能,要利用回溯?

ps:就是一种试错法,一旦发现不符合要求,那么利用递归后的语句来把之前修改的值重新修改为原来的值。

但是我依然没有想到怎么利用这个来解题。

万般无奈下,看了答案。原来把这个“被使用过的数字集合”也看做状态啊。。。

下面是python版答案(已经通过):

class Solution(object):
    def canIWin(self, maxChoosableInteger, desiredTotal):
        """
        :type maxChoosableInteger: int
        :type desiredTotal: int
        :rtype: bool
        """
        if desiredTotal<=0:
            return True
        if (1+maxChoosableInteger)*maxChoosableInteger//2<desiredTotal:
            return False
        state=['0' for x in range(maxChoosableInteger)]
        d={}
        def dfs(total,state,d):
            s=''.join(state)
            if d.get(s,None):
                return d[s]
            for i in range(len(state)):
                if state[i]=='0':
                    state[i]='1'
                    if total<=i+1 or dfs(total-i-1,state,d)==False:# 递归处
                        d[s]=True
                        state[i]='0'
                        return True
                    state[i]='0'
            d[s]=False
            return False
        return dfs(desiredTotal,state,d)

这题不可谓不精妙。

逐行说一说。

第一个if语句,如果目标值都小于等于0了,那你A随便拿一个数字都赢了 啊,还算好理解。

第二个if说的就是我在贪心算法中说到的那个情况,一直取最大,你都达不到目标值,还是拉倒吧,输了。

state是数字集合状态,我说说为什么用字符集来表示,首先你要能修改这个状态方便回溯,然后为了利用“缓存”,实际上就是dp里的带记忆的数组(这里是一个字典),我们需要让它是hashable(可哈希的),这样我考虑了一下,python中不能像java一样修改字符时新建对象,所以用join函数生成的不可变字符串来成键值。

然后就是一个深度优先搜索。最关键的地方当然是带标记的那个“递归处”,连上for循环实际上是一个检查性递归,递归函数dfs的含义实际上是吧题目的意思反向来解答:把这个数字减掉的目标值是否能用剩余的数字子集来加到。

字典d相当于缓存,如果这个状态已经被保存了,那就是说:这个状态,能/不能完成给定函数的目标值。

最终状态保存,并且返回修改。

感想:

本质上就是一个二状态的dp,只不过其中一个状态以字典实现,另一个用dfs求解。

第一次遇到这种二状态的dp,存一下。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值