📅  最后修改于: 2023-12-03 15:28:12.196000             🧑  作者: Mango
你和另一个人玩一种有趣的纸牌游戏。你们轮流从一副牌(没有大小王)中取牌,直到取完为止。每次取牌时,可以从最左侧或最右侧取走一张牌。每张牌上都带有一个正整数,表示你能获得的分数。假设你先手,双方都采用最优的策略,问你最终能获得多少分数?
这是一道经典的博弈论问题。可以用递归或动态规划两种方式求解。
递归:
假设此时牌堆为一个长度为n的序列a[1..n],最终你能获得的分数为f[1][n]。若此时该轮为你取牌,则你可选择从两端中的任意一端取走一张牌,获得对应的分数。
故此时你获得的分数为score=a[1]+max(f[2][n],f[1][n-1])。
若此时该轮为对手取牌,则对手可选择从两端中的任意一端取走一张牌。不妨假设他从最左侧取牌,则他在剩下的牌中变为先手,所获得的分数为a[1],你在剩下的牌中变为后手,所获得的分数为f[2][n]。故此时你获得的分数为min(f[2][n-1],f[1][n-1])。
综上,我们得出递推式:f[i][j] = max(a[i]+min(f[i+1][j-1], f[i+2][j]), a[j]+min(f[i+1][j-1], f[i][j-2]))。
边界情况为f[i][i]=a[i]。
最终结果为f[1][n]。
动态规划:
我们可以使用备忘录优化递归实现,也可以直接使用动态规划实现。
定义状态数组f[i][j]为从i到j范围内取牌时能获得的最大分数,则最终结果为f[1][n]。初始值为f[i][i]=a[i](i从1到n)。
对于长度为2的牌堆(即相邻的两张牌),我们可以直接根据规则获得最优解。
接下来我们考虑长度为3的牌堆,此时我们需要注意从哪一端取牌以及对手的取牌策略,具体如下:
我们可以依次类推,得到状态转移方程:f[i][j]=max(a[i]+min(f[i+2][j], f[i+1][j-1]), a[j]+min(f[i+1][j-1], f[i][j-2]))。
代码实现如下:
def card_game(nums: List[int]) -> int:
n = len(nums)
f = [[0] * n for _ in range(n)]
# 初始化长度为1的牌堆
for i in range(n):
f[i][i] = nums[i]
# 枚举长度为2至n的牌堆
for k in range(2, n+1):
for i in range(n-k+1):
j = i + k - 1
# 取两端的牌
score1 = nums[i] + min(f[i+2][j], f[i+1][j-1])
score2 = nums[j] + min(f[i+1][j-1], f[i][j-2])
f[i][j] = max(score1, score2)
# 取中间的牌
f[i][j] = max(f[i][j], nums[i+1] + min(f[i+1][j-1], f[i][j-2]))
return f[0][n-1]
本题是一道经典的博弈论问题,可用递归或动态规划两种方式实现。使用动态规划实现时,需要特别注意状态转移方程的对称性,以及长度为3的牌堆的处理方法。