Leetcode 1406:石子游戏 III(超详细的解法!!!)

Alice 和 Bob 用几堆石子在做游戏。几堆石子排成一行,每堆石子都对应一个得分,由数组 stoneValue 给出。

Alice 和 Bob 轮流取石子,Alice 总是先开始。在每个玩家的回合中,该玩家可以拿走剩下石子中的的前 1、2 或 3 堆石子 。比赛一直持续到所有石头都被拿走。

每个玩家的最终得分为他所拿到的每堆石子的对应得分之和。每个玩家的初始分数都是 0 。比赛的目标是决出最高分,得分最高的选手将会赢得比赛,比赛也可能会出现平局。

假设 Alice 和 Bob 都采取 最优策略 。如果 Alice 赢了就返回 “Alice” *,*Bob 赢了就返回 *“Bob”,*平局(分数相同)返回 “Tie”

示例 1:

输入:values = [1,2,3,7]
输出:"Bob"
解释:Alice 总是会输,她的最佳选择是拿走前三堆,得分变成 6 。但是 Bob 的得分为 7,Bob 获胜。

示例 2:

输入:values = [1,2,3,-9]
输出:"Alice"
解释:Alice 要想获胜就必须在第一个回合拿走前三堆石子,给 Bob 留下负分。
如果 Alice 只拿走第一堆,那么她的得分为 1,接下来 Bob 拿走第二、三堆,得分为 5 。之后 Alice 只能拿到分数 -9 的石子堆,输掉比赛。
如果 Alice 拿走前两堆,那么她的得分为 3,接下来 Bob 拿走第三堆,得分为 3 。之后 Alice 只能拿到分数 -9 的石子堆,同样会输掉比赛。
注意,他们都应该采取 最优策略 ,所以在这里 Alice 将选择能够使她获胜的方案。

示例 3:

输入:values = [1,2,3,6]
输出:"Tie"
解释:Alice 无法赢得比赛。如果她决定选择前三堆,她可以以平局结束比赛,否则她就会输。

示例 4:

输入:values = [1,2,3,-1,-2,-3,7]
输出:"Alice"

示例 5:

输入:values = [-1,-2,-3]
输出:"Tie"

提示:

  • 1 <= values.length <= 50000
  • -1000 <= values[i] <= 1000

解题思路

这个问题和Leetcode 1140:石子游戏 II,按照之前问题的第一种思路(也就是计算BobAlice两个人可以取的最大差值)。定义函数 f ( i ) f(i) f(i)表示最大差值,那么:

  • f ( i ) = m a x ( s u m ( s t o n e V a l u e [ i : i + x ] ) − f ( i + x ) ) ( 1 ≤ x ≤ 3 ) f(i)=max(sum(stoneValue[i:i+x])-f(i+x))(1\leq x \leq 3) f(i)=max(sum(stoneValue[i:i+x])f(i+x))(1x3)
from functools import lru_cache
class Solution:
    def stoneGameIII(self, stoneValue: List[int]) -> str:
        n = len(stoneValue)
        @lru_cache(None)
        def dfs(d):
            if d == n: return 0
            
            res = float("-inf")
            for i in range(1, 4):
                if d + i <= n:
                    res = max(res, sum(stoneValue[d:d + i]) - dfs(d + i))
            return res
        
        dif = dfs(0)
        if dif == 0: return "Tie"
        return "Alice" if dif > 0 else "Bob"

也可以参考第二种定义方式,定义 f ( i ) f(i) f(i)表示在stoneValue[i:]中对方取[1,3]堆石子后可获得的最多石子数,那么:

  • f ( i ) = s u m ( s t o n e V a l u e [ i : ] ) − m i n ( f ( i + x ) ) ( 1 ≤ x ≤ 3 ) f(i)=sum(stoneValue[i:])-min(f(i+x))(1\leq x \leq 3) f(i)=sum(stoneValue[i:])min(f(i+x))(1x3)

也就是对于Alice来说他所获得的石子数就是sum(stoneValue)减去Bob获得的石子数,此时对于Alice来说希望Bob获得的石子数尽量少。

from functools import lru_cache
class Solution:
    def stoneGameIII(self, stoneValue: List[int]) -> str:
        n, sum_all = len(stoneValue), stoneValue[-1]
        for i in range(n - 2, -1, -1):
            sum_all += stoneValue[i]
            stoneValue[i] += stoneValue[i + 1]
            
        @lru_cache(None)
        def dfs(i):
            if i == n: return 0
            return stoneValue[i] - min(dfs(i + x) for x in range(1, 4) if i + x <= n)
        
        Alice = dfs(0)
        if Alice * 2 == sum_all: return "Tie"
        return "Alice" if Alice * 2 > sum_all else "Bob"

上面这个代码会出现maximum recursion depth错误,可以采用动态规划的写法。

class Solution:
    def stoneGameIII(self, A: List[int]) -> str:
        n, sum_all = len(A), 0
        dp = [0] * (n + 1)
        for i in range(n - 1, -1, -1):
            sum_all += A[i]
            dp[i] = sum_all - min(dp[i + k] for k in range(1, 4) if i + k <= n)
        
        if dp[0] * 2 == sum_all: return "Tie"
        return "Alice" if dp[0] * 2 > sum_all else "Bob"

这个代码还可以优化,可以将空间复杂度优化到O(1)

class Solution:
    def stoneGameIII(self, A: List[int]) -> str:
        dp, n, sum_all = [0] * 3, len(A), 0
        for i in range(n - 1, -1, -1):
            sum_all += A[i]
            dp[i % 3] = sum_all - min(dp[(i + k) % 3] for k in range(1, 4))
        
        if dp[0] * 2 == sum_all: return "Tie"
        return "Alice" if dp[0] * 2 > sum_all else "Bob"

非常的简洁优雅!!!

我将该问题的其他语言版本添加到了我的GitHub Leetcode

如有问题,希望大家指出!!!

你可能感兴趣的:(leetcode解题指南,Problems)