🌈【LeetCode 486. 预测赢家 】- JavaScript(递归+DP)

235 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第20天,点击查看活动详情


说明:文章部分内容及图片出自网络,如有侵权请与我本人联系(主页有公众号:小攻城狮学前端)

作者:小只前端攻城狮、 主页:小只前端攻城狮的主页、 来源:掘金

GitHub:P-J27、 CSDN:PJ想做前端攻城狮

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。


【LeetCode 486. 预测赢家 】- JavaScript(递归+DP)

给你一个整数数组 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,而玩家 2 为 5 。 因此,玩家 1 永远不会成为赢家,返回 false 。

思路分析

这道题目有下面是个核心问题,解了它们这题就不在话下了。

  1. 怎么求出其中一个数组的最优解
  2. 怎么进行合并已经求出的两个数组的结果
  3. 如何进行数组的比较

可以说让人崩溃,做完这道题,相当于写了三道题。如果面试遇到这种题目,你应该懂面试官什么意思了,他对你不满意。

递归

题意描述

思路:diff返回(玩家1-玩家2)的分数差,玩家1希望该差越大越好,玩家2希望该差越小越好。

具体步骤:

  1. 首先,对原问题进行一个小的转换,玩家1分数 不小于 玩家2,则获胜。 可以转换成 玩家1分数 - 玩家2分数 >= 0 ,则玩家获胜。
  2. 分别遍历 玩家1 每次取首端,尾端的值,取最大值。
  3. 分别遍历 玩家2 每次取首端、尾端的值,取数值最大的数,由于进行了转换,总分数 -= 玩家2 的分数,所以应该取总分数的最小值。

var PredictTheWinner = function(nums) {
  return dfs(0, nums.length - 1, 0, 0, true);
  function dfs(start, end, score1, score2, canChoose) {
    if (start > end) {
      return score1 >= score2;
    }
    if (canChoose) {
      canChoose = !canChoose;
      if (dfs(start + 1, end, score1 + nums[start], score2, canChoose) || dfs(start, end - 1, score1 + nums[end], score2, canChoose)) {
        return true;
      }
    } else {
      canChoose = !canChoose;
      if (dfs(start + 1, end, score1, score2 + nums[start], canChoose) && dfs(start, end - 1, score1, score2 + nums[end], canChoose)) {
        return true;
      }
    }
    return false;
  }
};

动态规划

分析

如果假设玩家1在数组的任意一段位置[i, j]; 0 <= i <= j <= len-1;

  • 那么此时玩家1可以选择i或选择j;
  • 那么玩家2可以从剩余[i+1,j] 或[i, j-1]中选择最大值

使用 DP来记录用户选择的答案。那么可以推导出如下公式

dp[i][j] = Math.max(nums[i] - dp[i+1][j], nums[j] - dp[i][j-1])

如果我们是使用dp[i,j]来存储可以选择的最大值和。那么转移方程应该为

subSum = sum(i+1, j)
max(nums[i] + (subSum - dp[i+1][j]), nums[j] + (subSum - dp[i][j-1])) 
return dp[0][len-1] * 2 >= sum;

这样每次都需要计算子数组的和,麻烦有耗时就没有必要。

var PredictTheWinner = function(nums) {
    const n = nums.length
    const dp = new Array(n)
    for (let i = 0; i < n; i ++) {
        dp[i] = new Array(n)
        dp[i][i] = nums[i]
    }
    for (let i = n-2; i >= 0; i --) {
        for (let j = i+1; j < n; j ++) {
            const head = nums[i]-dp[i+1][j]
            const tail = nums[j]-dp[i][j-1]
            dp[i][j] = Math.max(head, tail)
        }
    }
    return dp[0][n-1] >= 0
};

感谢阅读,希望能对你有所帮助,文章若有错误或者侵权,可以在评论区留言或在我的主页添加公众号联系我。

写作不易,如果觉得不错,可以「点赞」+「评论」 谢谢支持❤