经典DP(一)博弈类

341 阅读3分钟

Summary

  • prefixSum 或者 minimax策略 都可以作为得到当前位置res的方式
  • 不要陷入greedy的陷阱 ❌:每一次的操作都需要minimal对手下一步的max收益

模版:DFS + memo

  • dfs(n):在条件 = n的情况下,先下手的player能不能赢
class Solution:
    def __init__(self):
        self.memo = {}
    
    def canIWin(self, n: int) -> bool:
        def dfs(n: int) -> bool:
            if n < 0:  # index边界
                return False
            if self.memo[n]:  # memo已有结果
                return self.memo[n]
            res = False  # init result
            for i in range(1, 4):  # 核心递推公式
                if n >= i:
                    res |= not dfs(n - i)
            self.memo[n] = res  # 保存结果到memo
            return res
        
        return dfs(n)  # call dfs

486. 预测赢家(Medium)

image.png

Solu:记忆化DFS

  • dfs(start, end) = 在nums[start, end+1]中,先走的player 比 后走的player 最多能多赢几分

image.png

Code:

class Solution:
    def PredictTheWinner(self, nums: List[int]) -> bool:
        memo = [[None] * len(nums) for _ in range(len(nums))]
        
        def dfs(start, end):  # 在 start 到 end 的区间内,先走的比后走的能最多多赢几分
            if start > end:
                return 0
            if memo[start][end]:
                return memo[start][end]
            memo[start][end] = max(nums[start] - dfs(start + 1, end), nums[end] - dfs(start, end - 1))
            return memo[start][end]
        
        return dfs(0, len(nums) - 1) >= 0


1025. 除数博弈(Easy)

image.png

Solu:记忆化DFS

  • dfs(n):选数字n的玩家能不能赢

Code:

class Solution:
    def divisorGame(self, n: int) -> bool:
        @lru_cache(None)
        def dfs(n):  # 选数字n的player能不能赢
            if n == 1:
                return False
            for i in range(1, n // 2 + 1):
                if n % i == 0 and not dfs(n - i):
                    return True
            return False
        
        # return n % 2 == 0
        return dfs(n)


Stone Game 系列

877. 石子游戏(Medium)

image.png

Solu:记忆化DFS

  • dfs(i, j):在piles[i:j+1]中,先下手的player比后下手的最多能赢多少分
    • 因为两个player都发挥了最佳水平,所以用max
  • 如果最佳水平的alice的得分都不能超过bob,那么alice必定输(return False);反之则反

Code:

class Solution:
    def stoneGame(self, piles: List[int]) -> bool:
        @lru_cache(None)
        def dfs(i, j) -> int:
            if i == j:
                return piles[i]
            return max(piles[i] - dfs(i + 1, j), piles[j] - dfs(i, j - 1))
        
        return dfs(0, len(piles) - 1) > 0


1140. 石子游戏 II(Medium)

image.png

Solu:记忆化DFS

  • dfs(idx, M):在M的条件下,在piles[idx:]里,先下手的player最多拿多少分
    • 策略:限制对手的得分 -> 最小化对手的最大得分(minimize max)
      • 先下手的player的得分 = sum(piles[idx:]) - min{opponent_score}
  • 前缀和:便于计算获得多少分

Code:

class Solution:
    def stoneGameII(self, piles: List[int]) -> int:
        preSum = [0]
        for i in range(len(piles)):
            preSum.append(preSum[-1] + piles[i])
        
        @lru_cache(None)
        def dfs(idx, M):
            if idx == len(piles):
                return 0
            if idx + 2 * M >= len(piles):
                return preSum[-1] - preSum[idx]
            res = float('inf')  # 对手拿的最大值
            for i in range(1, 2 * M + 1):  # 策略:minimize max
                res = min(res, dfs(idx + i, max(M, i)))
            return preSum[-1] - preSum[idx] - res
        
        return dfs(0, 1)


1406. 石子游戏 III(Hard)

image.png

Solu:记忆化DFS

  • dfs(idx):在stoneValue[idx:]中,先下手的player能比后下手的player最多多赢几分

Code:

class Solution:
    def stoneGameIII(self, stoneValue: List[int]) -> str:
        preSum = [0]
        for i in range(len(stoneValue)):
            preSum.append(preSum[-1] + stoneValue[i])
        
        @lru_cache(None)
        def dfs(idx) -> int:
            if idx >= len(stoneValue):
                return 0
            res = float('-inf')
            for i in range(min(3, len(stoneValue) - idx)):
                res = max(res, (preSum[idx + i + 1] - preSum[idx]) - dfs(idx + i + 1))
            return res
        
        if dfs(0) == 0:
            return "Tie"
        return "Alice" if dfs(0) > 0 else "Bob"


1510. 石子游戏 IV(Hard)

image.png

Solu:记忆化DFS

  • dfs(n):在还剩n个石头的情况下,先下手的palyer能不能赢
    • “我”在n的条件下能赢 = 对手在n - i * i的条件下会输

Code:

class Solution:
    def winnerSquareGame(self, n: int) -> bool:
        @lru_cache(None)
        def dfs(n) -> bool:
            if n == 0:
                return False
            for i in range(1, int(math.sqrt(n)) + 1):
                if not dfs(n - i ** 2):
                    return True
            return False
        
        return dfs(n)


Reference: