1872. 石子游戏 VIII
Alice 和 Bob 玩一个游戏,两人轮流操作, Alice 先手 。总共有 n n n 个石子排成一行。轮到某个玩家的回合时,如果石子的数目大于 1 ,他将执行以下操作:
选择一个整数 x > 1 x > 1 x>1 ,并且 移除 最左边的 x x x 个石子。将移除的石子价值之和累加到该玩家的分数中。将一个新的石子放在最左边,且新石子的值为被移除石子值之和。当只剩下一个石子时,游戏结束。Alice 和 Bob 的 分数之差 为 (Alice 的分数 - Bob 的分数) 。 Alice 的目标是最大化分数差,Bob 的目标是最小化分数差。给你一个长度为 n n n 的整数数组 s t o n e s stones stones ,其中 s t o n e s [ i ] stones[i] stones[i] 是 从左边起 第 i i i 个石子的价值。请你返回.在双方都采用最优策略的情况下,Alice 和 Bob 的 分数之差 。
示例 1:
输入:stones = [-1,2,-3,4,-5]
输出:5
解释:
- Alice 移除最左边的 4 个石子,得分增加 (-1) + 2 + (-3) + 4 = 2 ,并且将一个价值为 2 的石子放在最左边。stones = [2,-5] 。
- Bob 移除最左边的 2 个石子,得分增加 2 + (-5) = -3 ,并且将一个价值为 -3 的石子放在最左边。stones = [-3] 。
两者分数之差为 2 - (-3) = 5 。
提示:
n == stones.length2 <= n <= 10^5-10^4 <= stones[i] <= 10^4
思路:问题目标是Alice 的目标是最大化分数差,Bob 的目标是最小化分数差。这里的最大最小有点头疼,换一种说法,Alice 的目标是最大化(Alice 的分数 - Bob 的分数),Bob的目标是最大化(Bob的分数 - Alice 的分数),各自维护自己与对手的最大分数差。
每一次玩家至少拿两块石头,假设原先石子堆为stones = [-1,2,-3,4,-5],编号1-5,当用户某一次取到了编号最大的石子i,那么他这一次拿到的分数是 ∑ k = 1 i s t o n e k \sum_{k=1}^{i}stone_k ∑k=1istonek

如图所示,某一次Bob取到了编号最大为3的石子,得到的分数为 1 + ( − 3 ) = ( − 1 ) + 2 + ( − 3 ) = − 2 1+(-3) = (-1) + 2 +(-3) = -2 1+(−3)=(−1)+2+(−3)=−2
本题需要使用动态规划求解,但不必记录每一个过程中Alice,Bob的分数差记录。记dp[i]表示,玩家(不用区分是Alice,还是Bob,因为两个人的目标都是是自己和对手的分数差最大)取编号[i,n]的中某一块石子后,能够产生的最大分数差。状态转移方程:
d
p
[
i
]
=
m
a
x
{
s
t
o
n
e
s
[
1
]
+
.
.
.
+
s
t
o
n
e
s
[
j
]
−
d
p
[
j
+
1
]
}
,
j
≥
i
dp[i]=max\{stones[1]+...+stones[j]-dp[j+1]\},j \geq i
dp[i]=max{stones[1]+...+stones[j]−dp[j+1]},j≥i
class Solution {
public:
int stoneGameVIII(vector<int>& stones) {
int len = stones.size();
vector<int> presum(len+1,0);
for(int i=1;i<=len;i++)
presum[i] = presum[i-1] + stones[i-1];
vector<int> dp(len+1, INT_MIN);
for(int i=len;i>=0;i--){
dp[i] = presum[len];
for(int j=i;j<len;j++){
dp[i] = max(dp[i],presum[j]-dp[j+1]);
}
}
return dp[2];
}
};
算法复杂度 O ( N 2 ) O(N^2) O(N2),程序会超时。
代码存在冗余,
for(int i=len;i>=0;i--){
dp[i] = presum[len];
for(int j=i;j<len;j++){
dp[i] = max(dp[i],presum[j]-dp[j+1]);
}
}
改进思路:
-
presum[j]-dp[j+1]在过程中需要要维护一个最大值即可,不需要重复计算。
-
dp[i+1]就记录了取[i+1,n]的石子带来的最大分数差,所以只需要再计算presum[i]-dp[j+1]
两者思路其实是一样的,取第二点优化,状态转移方程可以变为:
d
p
[
i
]
=
m
a
x
{
d
p
[
i
+
1
]
,
s
t
o
n
e
s
[
1
]
+
.
.
.
+
s
t
o
n
e
s
[
i
]
−
d
p
[
i
+
1
]
}
dp[i]=max\{dp[i+1],stones[1]+...+stones[i]-dp[i+1]\}
dp[i]=max{dp[i+1],stones[1]+...+stones[i]−dp[i+1]}
class Solution {
public:
int stoneGameVIII(vector<int>& stones) {
int len = stones.size();
vector<int> presum(len+1,0);
for(int i=1;i<=len;i++)
presum[i] = presum[i-1] + stones[i-1];
vector<int> dp(len+1);
dp[len] = presum[len];
for(int i=len-1;i>=0;i--){
dp[i] = max(dp[i+1],presum[i]-dp[i+1]);
}
return dp[2];
}
};
注意:程序最后返回的dp[2]原因是,Alice作为先手第一次必须要至少取到编号2的石子。
问题主要的思路点就是,不再区分谁取了石子,而是用dp[i]表示取了编号[i,n]的中某一块石子后,能够产生的最大分数差。
相似题目:

本文探讨了Alice和Bob在石子游戏中如何通过动态规划实现最优策略,以最大化分数差。关键在于理解每个玩家的策略目标并利用dp数组高效计算最大收益。
810

被折叠的 条评论
为什么被折叠?



