1690. 石子游戏 VII

石子游戏 VII:博弈问题的动态规划解法

目录

【算法题解】石子游戏 VII:最大得分差的博弈问题

题目描述

题目举例(示意)

解题分析

解决思路:动态规划

定义状态

状态转移

边界条件

预处理优化

代码实现

复杂度分析

示例说明

小结


【算法题解】石子游戏 VII:最大得分差的博弈问题

题目描述

在「石子游戏 VII」中,爱丽丝和鲍勃轮流从一排石子中取走石子,爱丽丝先手。共有 n 块石子,排成一排,每块石子有一个整数值。两人轮流行动,每次只能从当前石子行的最左端或最右端拿走一个石子。

关键规则:

  • 拿走石子后,该玩家获得的得分等于剩余石子值之和
  • 当没有石子可以拿时游戏结束。
  • 两人都采用最优策略,爱丽丝希望最大化最终得分差(爱丽丝得分 - 鲍勃得分),而鲍勃想尽量减少这个差值。

请你计算,在双方都最优的情况下,最终的得分差是多少。


题目举例(示意)

假设石子行是 [5, 3, 1, 4, 2]

  • 初始总和为 5 + 3 + 1 + 4 + 2 = 15
  • 爱丽丝先手,可以取左边的 5 或右边的 2,但拿走后得分是剩余石子的和,不是石子本身的值。
  • 如果爱丽丝拿走左边的 5,剩余石子为 [3, 1, 4, 2],得分为 10
  • 如果拿走右边的 2,剩余石子为 [5, 3, 1, 4],得分为 13
    然后鲍勃继续选,双方都尽力最大化各自利益,最终求出差值。

解题分析

这是一个典型的博弈问题,要求双方在最优策略下对差值的最大化(或最小化)。

关键点在于:

  • 当前玩家拿走的是石子,但获得的是剩余石子的值之和。
  • 这意味着玩家的得分依赖于剩下的石子和,与常见“拿走石子直接得分”不同。
  • 玩家选择后,局面缩小,转由对手进行同样的选择。
  • 我们希望计算最终两人得分的差值。

解决思路:动态规划

定义状态

dp[i][j] 表示当前玩家面对区间 [i, j] 的石子时,能获得的最大得分差。这里的得分差是“当前玩家得分 - 另一个玩家得分”。

状态转移

当前玩家可以从区间两端取石子:

  • 取左端 stones[i]
    玩家获得的得分是剩余石子和,即区间 [i+1, j] 的和 sum(i+1,j)
    之后剩下 [i+1, j],轮到对手操作,对手能取得的最大差值是 dp[i+1][j],这对当前玩家来说是负数(对手得分 - 当前玩家得分)。
    因此取左端的净得分差为:
    sum(i+1,j) - dp[i+1][j]
  • 取右端 stones[j]
    同理,得分为 sum(i, j-1),后续差值为 dp[i][j-1],净得分差:
    sum(i, j-1) - dp[i][j-1]

最终选择两者中较大的作为 dp[i][j]

dp[i,j]=max⁡(sum(i+1,j)−dp[i+1,j],sum(i,j−1)−dp[i,j−1])dp[i,j] = \max\big( sum(i+1,j) - dp[i+1,j],\quad sum(i,j-1) - dp[i,j-1] \big)

边界条件

当区间只剩一个石子,即 i == j,当前玩家拿走这唯一石子后,没有剩余石子,得分为0:

dp[i,i]=0dp[i,i] = 0


预处理优化

计算区间和 sum(i,j) 可以通过前缀和数组 prefix_sum 优化,使得求区间和变成 O(1) 时间。


代码实现

from typing import List

class Solution:
    def stoneGameVII(self, stones: List[int]) -> int:
        n = len(stones)
        
        # 计算前缀和,prefix_sum[i] 表示 stones[0:i] 的和
        prefix_sum = [0] * (n+1)
        for i in range(n):
            prefix_sum[i+1] = prefix_sum[i] + stones[i]
        
        def get_sum(i, j):
            return prefix_sum[j+1] - prefix_sum[i]
        
        dp = [[0] * n for _ in range(n)]
        
        # 处理区间长度从2到n
        for length in range(2, n+1):
            for i in range(n - length + 1):
                j = i + length - 1
                # 取左边石子
                left_choice = get_sum(i+1, j) - dp[i+1][j]
                # 取右边石子
                right_choice = get_sum(i, j-1) - dp[i][j-1]
                dp[i][j] = max(left_choice, right_choice)
        
        return dp[0][n-1]

复杂度分析

  • 时间复杂度:
    外层循环枚举区间长度 O(n),内层循环遍历起点 O(n),状态转移常数时间,整体 O(n^2)
  • 空间复杂度:
    使用了 dp 二维数组和 prefix_sum 数组,空间为 O(n^2)

示例说明

以输入 [5, 3, 1, 4, 2] 为例:

  • 总和为 15。
  • 爱丽丝先手,选择拿左边 5 或右边 2
  • 拿左边剩余 [3,1,4,2],得分为 10。拿右边剩余 [5,3,1,4],得分为 13。
  • 然后轮到鲍勃,他会做出最佳反制选择。
  • 最终 dp 计算得到爱丽丝和鲍勃得分差最大为 8(具体过程依赖递归展开和状态转移)。

小结

  • 这题是一类典型的两人博弈动态规划问题。
  • 状态设计核心是「当前玩家最大得分差」,利用「取石后剩余石子和减去对手最优差值」构建转移。
  • 使用前缀和优化区间和计算。
  • 代码简洁且高效,适合掌握博弈 DP 模板。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值