LeetCode探索(36):486-预测赢家

190 阅读3分钟

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

题目

给你一个整数数组 nums 。玩家 1 和玩家 2 基于这个数组设计了一个游戏。

玩家 1 和玩家 2 轮流进行自己的回合,玩家 1 先手。开始时,两个玩家的初始分值都是 0 。每一回合,玩家从数组的任意一端取一个数字(即,nums[0]nums[nums.length - 1]),取到的数字将会从数组中移除(数组长度减 1 )。玩家选中的数字将会加到他的得分上。当数组中没有剩余数字可取时,游戏结束。

如果玩家 1 能成为赢家,返回 true 。如果两个玩家得分相等,同样认为玩家 1 是游戏的赢家,也返回 true 。你可以假设每个玩家的玩法都会使他的分数最大化。

示例 1:

输入:nums = [1,5,2]
输出:false
解释:一开始,玩家 1 可以从 1 和 2 中进行选择。
如果他选择 2(或者 1 ),那么玩家 2 可以从 1(或者 2 )和 5 中进行选择。如果玩家 2 选择了 5 ,那么玩家 1 则只剩下 1(或者 2 )可选。 
所以,玩家 1 的最终分数为 1 + 2 = 3,而玩家 25 。
因此,玩家 1 永远不会成为赢家,返回 false 。

示例 2:

输入:nums = [1,5,233,7]
输出:true
解释:玩家 1 一开始选择 1 。然后玩家 2 必须从 57 中进行选择。无论玩家 2 选择了哪个,玩家 1 都可以选择 233 。
最终,玩家 1234 分)比玩家 212 分)获得更多的分数,所以返回 true,表示玩家 1 可以成为赢家。

提示:

  • 1 <= nums.length <= 20
  • 0 <= nums[i] <= 10^7

思考

这是一道有意思的题目,难度中等。跟本题类似的题目是 石子游戏

我们可以使用动态规划的方法去解决这个问题。

为了判断哪个玩家可以获胜,我们定义了一个总分,即先手得分与后手得分之差。这里我们定义了二维数组 dp,其行数和列数都等于数组的索引,dp[i][j] 表示在下标范围 [i, j]中,当前玩家与另一个玩家的得分之差。

当 i = j 时,只剩下一个元素,当前玩家只能取走这个元素,因此 dp[i][i] = nums[i]

当 i < j 时,当前玩家可以选择取走 nums[i]nums[j],当前玩家会选择最优的方案。状态转移方程为:

dp[i][j] = max(nums[i] − dp[i+1][j], nums[j] − dp[i][j−1])

最后,我们判断 dp[0][nums.length − 1] 的值,如果大于或等于 0,则先手的玩家 1 赢得比赛,否则后手的玩家 2 赢得比赛。

解答

方法一:动态规划

/**
 * @author 觅迹寻踪
 * @param {number[]} nums
 * @return {boolean}
 */
var PredictTheWinner = function(nums) {
  const length = nums.length
  const dp = new Array(length).fill(0).map(() => new Array(length).fill(0))
  for (let i = 0; i < length; i++) {
    dp[i][i] = nums[i]
  }
  for (let i = length - 2; i >= 0; i--) {
    for (let j = i + 1; j < length; j++) {
      dp[i][j] = Math.max(nums[i] - dp[i + 1][j], nums[j] - dp[i][j - 1])
    }
  }
  return dp[0][length - 1] >= 0
}
// 执行用时:68 ms, 在所有 JavaScript 提交中击败了88.89%的用户
// 内存消耗:41.5 MB, 在所有 JavaScript 提交中击败了18.52%的用户

复杂度分析

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

方法二:优化

考虑到状态转移方程中,dp[i][j] 的值只和 dp[i+1][j]dp[i][j−1] 有关,即在计算 dp 的第 i 行的值时,只需要使用到第 i 行和第 i+1 行的值,因此我们这里可以使用一维数组代替二维数组,对空间复杂度进行优化。

/**
 * @author 觅迹寻踪
 * @param {number[]} nums
 * @return {boolean}
 */
var PredictTheWinner = function(nums) {
  const length = nums.length
  const dp = new Array(length).fill(0)
  for (let i = 0; i < length; i++) {
    dp[i] = nums[i]
  }
  for (let i = length - 2; i >= 0; i--) {
    for (let j = i + 1; j < length; j++) {
      dp[j] = Math.max(nums[i] - dp[j], nums[j] - dp[j - 1])
    }
  }
  return dp[length - 1] >= 0
}
// 执行用时:60 ms, 在所有 JavaScript 提交中击败了100.00%的用户
// 内存消耗:41.1 MB, 在所有 JavaScript 提交中击败了19.45%的用户

复杂度分析

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

参考