「这是我参与2022首次更文挑战的第23天,活动详情查看:2022首次更文挑战」。
题目
爱丽丝和鲍勃继续他们的石子游戏。许多堆石子 排成一行,每堆都有正整数颗石子 piles[i]。游戏以谁手中的石子最多来决出胜负。
爱丽丝和鲍勃轮流进行,爱丽丝先开始。最初,M = 1。
在每个玩家的回合中,该玩家可以拿走剩下的 前 X 堆的所有石子,其中 1 <= X <= 2M。然后,令 M = max(M, X)。
游戏一直持续到所有石子都被拿走。
假设爱丽丝和鲍勃都发挥出最佳水平,返回爱丽丝可以得到的最大数量的石头。
示例 1:
输入:piles = [2,7,9,4,4]
输出:10
解释:如果一开始Alice取了一堆,Bob取了两堆,然后Alice再取两堆。爱丽丝可以得到2 + 4 + 4 = 10堆。如果Alice一开始拿走了两堆,那么Bob可以拿走剩下的三堆。在这种情况下,Alice得到2 + 7 = 9堆。返回10,因为它更大。
示例 2:
输入:piles = [1,2,3,4,5,100]
输出:104
提示:
1 <= piles.length <= 1001 <= piles[i] <= 10^4
思考
这是一道博弈论类型的题目,难度中等。乍一看题目有点复杂,相比于石子游戏 I ,本题中玩家可以拿走剩下的 前 X 堆的所有石子,而数字 X 是可选的。
我们可以借助动态规划的方法去解决该问题。
为了方便统计玩家获取的石子数量之和,我们可以借助后缀和,将区间 [i..len - 1] 的后缀和定义为 suffixSum[i],那么区间 [i..j] 的后缀和为suffixSum[i] - suffixSum[j - 1]。考虑到每次取出的石子数量是可变的,那么我们需要将每回合可以拿走的石子数量考虑进来。
我们定义二维数组 dp[i][j],表示从区间 piles[i..len - 1] 里取出 j 堆石子,当前先手能够获得的分数。接着我们就可以列出状态转移方程了。边界条件是,当i + 2 * M >= len时,玩家可以取走[i, len - 1]中的所有堆。
总而言之,本题相比于石子游戏 I ,还是复杂了很多。
解答
方法一:动态规划
/**
* @param {number[]} piles
* @return {number}
*/
var stoneGameII = function(piles) {
const len = piles.length
// 后缀和
const suffixSum = new Array(len + 1).fill(0)
for (let i = len - 1; i >= 0; i--) {
suffixSum[i] = suffixSum[i + 1] + piles[i]
}
// dp[i][j]
const dp = new Array(len).fill(0).map(() => new Array(len + 1).fill(0))
for (let i = len - 1; i >= 0; i--) {
// 枚举 M:从取左边 1 堆石头,到 len 堆石头
for (let M = 1; M <= len; M++) {
// [i, len - 1] 这个区间里所有的石头都可以拿走,因为玩家一次最多可以取走 2M 堆石子
if (i + 2 * M >= len) {
dp[i][M] = suffixSum[i]
continue
}
// 枚举 X
for (let X = 1; X <= 2 * M; X++) {
dp[i][M] = Math.max(dp[i][M], suffixSum[i] - dp[i + X][Math.max(X, M)])
}
}
}
return dp[0][1]
}
// console.log(stoneGameII([2,7,9,4,4])) // 10
// console.log(stoneGameII([1,2,3,4,5,100])) // 104