Hard
题目描述
Alice 和 Bob 继续他们的石子游戏。几块石子 排成一行,每块石子都有一个关联的数值,该数值由数组 stoneValue 给出。
Alice 和 Bob 轮流取石子,Alice 先开始。在每个玩家的回合中,该玩家可以拿走剩下石子中的前 1,2,或 3 块石子。
每个玩家的得分为他拿到的石子的数值总和。每个玩家的初始分数都是 0。
游戏的目标是结束时分数最高,分数最高的一方获胜。如果两个玩家的分数相同,那么这局游戏平局。游戏继续进行,直到所有石子都被拿走。
假设 Alice 和 Bob 都会采用 最优策略。
如果 Alice 会获胜就返回 "Alice",Bob 获胜则返回 "Bob",平局则返回 "Tie"。
示例 1:
输入:stoneValue = [1,2,3,7]
输出:"Bob"
解释:Alice 总是会输。她的最佳移动是拿走前三堆,得分变成 6。现在 Bob 的得分为 7,Bob 获胜。
示例 2:
输入:stoneValue = [1,2,3,-9]
输出:"Alice"
解释:Alice 必须在第一步选择所有三堆才能获胜,给 Bob 留下负分。
如果 Alice 选择一堆,她的分数将是 1,下一步 Bob 的分数变成 5。在下一步中,Alice 将拿走值为 -9 的堆并败北。
如果 Alice 选择两堆,她的分数将是 3,下一步 Bob 的分数变成 3。在下一步中,Alice 将拿走值为 -9 的堆,也会败北。
记住两人都采用最优策略,所以 Alice 会选择能让她获胜的方案。
示例 3:
输入:stoneValue = [1,2,3,6]
输出:"Tie"
解释:Alice 无法赢得这场比赛。如果她决定选择前三堆,她可以平局结束游戏,否则她会输。
提示:
1 <= stoneValue.length <= 5 * 10^4-1000 <= stoneValue[i] <= 1000
解题思路
解题思路
这是一个典型的博弈论动态规划问题。我们需要考虑双方都采用最优策略的情况。
核心思想
- 相对得分思维:与其分别计算 Alice 和 Bob 的得分,不如计算他们的相对得分差值
- 状态定义:
dp[i]表示从位置i开始到游戏结束,当前玩家相对于对手能获得的最大得分差 - 状态转移:当前玩家可以选择拿走 1、2 或 3 块石子,每种选择都会影响后续的相对得分
递推关系
对于位置 i,当前玩家有三种选择:
- 拿 1 块石子:得分为
sum[i],剩余部分对手先手,相对得分差为sum[i] - dp[i+1] - 拿 2 块石子:得分为
sum[i] + sum[i+1],相对得分差为sum[i] + sum[i+1] - dp[i+2] - 拿 3 块石子:得分为
sum[i] + sum[i+1] + sum[i+2],相对得分差为sum[i] + sum[i+1] + sum[i+2] - dp[i+3]
当前玩家会选择能使自己相对得分最大的策略,所以:
dp[i] = max(三种选择的相对得分差)
实现要点
- 从后往前计算,因为后面的状态会影响前面的决策
- 边界处理:当剩余石子少于选择数量时,只能拿完所有剩余石子
- 最终判断:
dp[0]就是 Alice 相对于 Bob 的得分差,根据正负性判断胜负
代码实现
class Solution {
public:
string stoneGameIII(vector<int>& stoneValue) {
int n = stoneValue.size();
vector<int> dp(n + 3, 0); // dp[i] 表示从位置i开始,当前玩家相对对手的最大得分差
// 从后往前计算
for (int i = n - 1; i >= 0; i--) {
int take = 0;
dp[i] = INT_MIN;
// 尝试拿1、2、3块石子
for (int k = 1; k <= 3 && i + k - 1 < n; k++) {
take += stoneValue[i + k - 1];
dp[i] = max(dp[i], take - dp[i + k]);
}
}
if (dp[0] > 0) return "Alice";
else if (dp[0] < 0) return "Bob";
else return "Tie";
}
};
class Solution:
def stoneGameIII(self, stoneValue: List[int]) -> str:
n = len(stoneValue)
dp = [0] * (n + 3) # dp[i] 表示从位置i开始,当前玩家相对对手的最大得分差
# 从后往前计算
for i in range(n - 1, -1, -1):
take = 0
dp[i] = float('-inf')
# 尝试拿1、2、3块石子
for k in range(1, 4):
if i + k - 1 < n:
take += stoneValue[i + k - 1]
dp[i] = max(dp[i], take - dp[i + k])
if dp[0] > 0:
return "Alice"
elif dp[0] < 0:
return "Bob"
else:
return "Tie"
public class Solution {
public string StoneGameIII(int[] stoneValue) {
int n = stoneValue.Length;
int[] dp = new int[n + 3]; // dp[i] 表示从位置i开始,当前玩家相对对手的最大得分差
// 从后往前计算
for (int i = n - 1; i >= 0; i--) {
int take = 0;
dp[i] = int.MinValue;
// 尝试拿1、2、3块石子
for (int k = 1; k <= 3 && i + k - 1 < n; k++) {
take += stoneValue[i + k - 1];
dp[i] = Math.Max(dp[i], take - dp[i + k]);
}
}
if (dp[0] > 0) return "Alice";
else if (dp[0] < 0) return "Bob";
else return "Tie";
}
}
var stoneGameIII = function(stoneValue) {
const n = stoneValue.length;
const dp = new Array(n + 3).fill(0); // dp[i] 表示从位置i开始,当前玩家相对对手的最大得分差
// 从后往前计算
for (let i = n - 1; i >= 0; i--) {
let take = 0;
dp[i] = -Infinity;
// 尝试拿1、2、3块石子
for (let k = 1; k <= 3 && i + k - 1 < n; k++) {
take += stoneValue[i + k - 1];
dp[i] = Math.max(dp[i], take - dp[i + k]);
}
}
if (dp[0] > 0) return "Alice";
else if (dp[0] < 0) return "Bob";
else return "Tie";
};
复杂度分析
| 复杂度类型 | 分析 |
|---|---|
| 时间复杂度 | O(n) - 每个位置计算常数次(最多3次选择) |
| 空间复杂度 | O(n) - 需要dp数组存储状态 |
相关题目
. Stone Game V (Hard)
. Stone Game VI (Medium)
. Stone Game VII (Medium)
. Stone Game VIII (Hard)
. Stone Game IX (Medium)