877 石子游戏

本文最后更新于:2021年1月9日 下午

亚历克斯和李用几堆石子在做游戏。偶数堆石子排成一行,每堆都有正整数颗石子 piles[i]

游戏以谁手中的石子最多来决出胜负。石子的总数是奇数,所以没有平局。

亚历克斯和李轮流进行,亚历克斯先开始。 每回合,玩家从行的开始或结束处取走整堆石头。 这种情况一直持续到没有更多的石子堆为止,此时手中石子最多的玩家获胜。

假设亚历克斯和李都发挥出最佳水平,当亚历克斯赢得比赛时返回 true ,当李赢得比赛时返回 false

示例:

1
2
3
4
5
6
7
8
输入:[5,3,4,5]
输出:true
解释:
亚历克斯先开始,只能拿前 5 颗或后 5 颗石子 。
假设他取了前 5 颗,这一行就变成了 [3,4,5] 。
如果李拿走前 3 颗,那么剩下的是 [4,5],亚历克斯拿走后 5 颗赢得 10 分。
如果李拿走后 5 颗,那么剩下的是 [3,4],亚历克斯拿走后 4 颗赢得 9 分。
这表明,取前 5 颗石子对亚历克斯来说是一个胜利的举动,所以我们返回 true 。

提示:

  1. 2 <= piles.length <= 500
  2. piles.length 是偶数。
  3. 1 <= piles[i] <= 500
  4. sum(piles) 是奇数。

Solution

参考思路:@LeetCode官方

  • 数学法,先手必赢

  • 动态规划

    定义二维数组 $\textit{dp}$,其行数和列数都等于石子的堆数,$\textit{dp}[i][j]$表示当剩下的石子堆为下标 i到下标 j 时,当前玩家与另一个玩家的石子数量之差的最大值,注意当前玩家不一定是先手 $\text{Alex}$ 。

    • 只有当 $i \le j$ 时,剩下的石子堆才有意义,因此当 i>j 时,$\textit{dp}[i][j]=0$ 。

    • 当 i=ji=j 时,只剩下一堆石子,当前玩家只能取走这堆石子,因此对于所有 $0 \le i < \textit{nums}.\text{length}$,都有$ \textit{dp}[i][i]=\textit{piles}[i]$。

    • 当 i<ji<j 时,当前玩家可以选择取走 piles[i] 或 piles[j],然后轮到另一个玩家在剩下的石子堆中取走石子。在两种方案中,当前玩家会选择最优的方案,使得自己的石子数量最大化。因此可以得到如下状态转移方程:

    $\textit{dp}[i][j]=\max(\textit{piles}[i] - \textit{dp}[i + 1][j], \textit{piles}[j] - \textit{dp}[i][j - 1])$

    最后判断 $\textit{dp}[0][\textit{piles}.\text{length}-1]$的值,如果大于 0,则$ \text{Alex}$ 的石子数量大于 $\text{Lee}$的石子数量,因此 $\text{Alex}$ 赢得比赛,否则 Lee 赢得比赛。

1
2
3
4
5
6
7
8
9
10
class Solution:
def stoneGame(self, piles: List[int]) -> bool:
length = len(piles)
dp = [[0] * length for _ in range(length)]
for i, pile in enumerate(piles):
dp[i][i] = pile
for i in range(length - 2, -1, -1):
for j in range(i + 1, length):
dp[i][j] = max(piles[i] - dp[i + 1][j], piles[j] - dp[i][j - 1])
return dp[0][length - 1] > 0

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!