LeetCode探索(38):1406-石子游戏III

194 阅读2分钟

「这是我参与2022首次更文挑战的第24天,活动详情查看:2022首次更文挑战」。

题目

Alice 和 Bob 用几堆石子在做游戏。几堆石子排成一行,每堆石子都对应一个得分,由数组 stoneValue 给出。

Alice 和 Bob 轮流取石子,Alice 总是先开始。在每个玩家的回合中,该玩家可以拿走剩下石子中的的前 1、2 或 3 堆石子 。比赛一直持续到所有石头都被拿走。

每个玩家的最终得分为他所拿到的每堆石子的对应得分之和。每个玩家的初始分数都是 0 。比赛的目标是决出最高分,得分最高的选手将会赢得比赛,比赛也可能会出现平局。

假设 Alice 和 Bob 都采取 最优策略 。如果 Alice 赢了就返回 "Alice" *,*Bob 赢了就返回 *"Bob",*平局(分数相同)返回 "Tie"

示例 1:

输入:values = [1,2,3,7]
输出:"Bob"
解释:Alice 总是会输,她的最佳选择是拿走前三堆,得分变成 6 。但是 Bob 的得分为 7,Bob 获胜。

示例 2:

输入:values = [1,2,3,-9]
输出:"Alice"
解释:Alice 要想获胜就必须在第一个回合拿走前三堆石子,给 Bob 留下负分。
如果 Alice 只拿走第一堆,那么她的得分为 1,接下来 Bob 拿走第二、三堆,得分为 5 。之后 Alice 只能拿到分数 -9 的石子堆,输掉比赛。
如果 Alice 拿走前两堆,那么她的得分为 3,接下来 Bob 拿走第三堆,得分为 3 。之后 Alice 只能拿到分数 -9 的石子堆,同样会输掉比赛。
注意,他们都应该采取 最优策略 ,所以在这里 Alice 将选择能够使她获胜的方案。

示例 3:

输入:values = [1,2,3,6]
输出:"Tie"
解释:Alice 无法赢得比赛。如果她决定选择前三堆,她可以以平局结束比赛,否则她就会输。

示例 4:

输入:values = [1,2,3,-1,-2,-3,7]
输出:"Alice"

示例 5:

输入:values = [-1,-2,-3]
输出:"Tie"

提示:

  • 1 <= values.length <= 50000
  • -1000 <= values[i] <= 1000

思考

这道题是石子游戏系列题目的第三题,难度困难。结合前面做石子游戏题目的经验,我们可以考虑借助动态规划去解决该问题。

我们试着定义数组f[i],表示在剩下的区间 [i .. n-1] 的石子中,当前玩家累计可以拿得的石子数。我们用 sum(l,r) 表示第 l, l+1, ⋯, r 堆石子的的数量之和,这也就是后缀和的概念。那么,在剩下的区间 [i .. n-1] 的石子中,

如果当前玩家选择了1堆石子,则当前玩家可以拿到的石子数是 sum(i, n−1) − f[i+1]

如果当前玩家选择了2堆石子,则当前玩家可以拿到的石子数是 sum(i, n−1) − f[i+2]

如果当前玩家选择了3堆石子,则当前玩家可以拿到的石子数是 sum(i, n−1) − f[i+3]

最后,f[0] 就表示 Alice 最多可以获得的石子数量,我们将 f[0] * 2 与 sum[0, n-1] 进行比较,则可以得到该题目的答案。

总而言之,这道题还是有一定的难度,需要我们去思考如何定义状态并写出状态转移方程。加油奥力给!

解答

方法一:动态规划

/**
 * @author 觅迹寻踪
 * @param {number[]} stoneValue
 * @return {string}
 */
var stoneGameIII = function(stoneValue) {
  const n = stoneValue.length
  // 后缀和数组
  const suffixSum = new Array(n).fill(0)
  suffixSum[n - 1] = stoneValue[n - 1]
  for (let i = n - 2; i >= 0; --i) {
    suffixSum[i] = suffixSum[i + 1] + stoneValue[i]
  }
  // 数组 f
  const f = new Array(n + 1).fill(0)
  // 边界情况,当没有石子时,分数为 0
  f[n] = 0
  for (let i = n - 1; i >= 0; --i) {
    let bestj = f[i + 1]
    for (let j = i + 2; j <= i + 3 && j <= n; ++j) {
      bestj = Math.min(bestj, f[j])
    }
    f[i] = suffixSum[i] - bestj
  }
  // f[0] * 2 与 total 进行比较
  let total = 0
  for (let value of stoneValue) {
    total += value
  }
  return f[0] * 2 === total ? "Tie" : (f[0] * 2 > total ? "Alice" : "Bob")
}

复杂度分析

  • 时间复杂度:O(N),其中 N 是数组 values 的长度。
  • 空间复杂度:O(N)。

参考