leetcode 486. Predict the Winner

来源:互联网 发布:数据库概念模式 编辑:程序博客网 时间:2024/06/11 13:00

Given an array of scores that are non-negative integers. Player 1 picks one of the numbers from either end of the array followed by the player 2 and then player 1 and so on. Each time a player picks a number, that number will not be available for the next player. This continues until all the scores have been chosen. The player with the maximum score wins.

Given an array of scores, predict whether player 1 is the winner. You can assume each player plays to maximize his score.

Example 1:

Input: [1, 5, 2]Output: FalseExplanation: Initially, player 1 can choose between 1 and 2. 
If he chooses 2 (or 1), then player 2 can choose from 1 (or 2) and 5. If player 2 chooses 5, then player 1 will be left with 1 (or 2).
So, final score of player 1 is 1 + 2 = 3, and player 2 is 5.
Hence, player 1 will never be the winner and you need to return False.

Example 2:

Input: [1, 5, 233, 7]Output: TrueExplanation: Player 1 first chooses 1. Then player 2 have to choose between 5 and 7. No matter which number player 2 choose, player 1 can choose 233.
Finally, player 1 has more score (234) than player 2 (12), so you need to return True representing player1 can win.

Note:

  1. 1 <= length of the array <= 20.
  2. Any scores in the given array are non-negative integers and will not exceed 10,000,000.
  3. If the scores of both players are equal, then player 1 is still the winner.
一道非常好的思维题。(看看solutions吧,各种解法都有

solutions:https://leetcode.com/problems/predict-the-winner/#/solution

因为需要使player1's score > player2‘s score,即player1's score - player2‘s score > 0。所以,我们可以计算player1's score - player2‘s score , 如果当前turn是player1的,则加上当前选择的数字,如果当前turn是player2的,则减去当前选择的数字。

package leetcode;public class Predict_the_Winner_486 {public boolean PredictTheWinner(int[] nums) {int number=winner(nums, 0, nums.length-1, 1);if(number>=0){return true;}else{return false;}}public int winner(int[] nums,int low,int high,int isPlayer1Turn){if(low==high){return isPlayer1Turn*nums[low];}int pickFirst=isPlayer1Turn*nums[low]+winner(nums, low+1, high, -isPlayer1Turn);int pickLast=isPlayer1Turn*nums[high]+winner(nums, low, high-1, -isPlayer1Turn);int max=0;if(isPlayer1Turn==1){max=Math.max(pickFirst, pickLast);}else{max=Math.min(pickFirst, pickLast);//如果是player2的turn,是希望player1获取利益最少的}return max;}public static void main(String[] args) {// TODO Auto-generated method stubPredict_the_Winner_486 p=new Predict_the_Winner_486();int[] nums=new int[]{1,5,233,7};System.out.println(p.PredictTheWinner(nums));}}
大神则更简洁:winner函数只计算当前turn的player获取利益最多的值。在递归时,下一turn肯定不是当前player,所以是减去下一递归的函数结果。
public class Solution {    public boolean PredictTheWinner(int[] nums) {        return winner(nums, 0, nums.length - 1) >= 0;    }    public int winner(int[] nums, int s, int e) {        if (s == e)            return nums[s];        int a = nums[s] - winner(nums, s + 1, e);        int b = nums[e] - winner(nums, s, e - 1);        return Math.max(a, b);    }}
大神说,还可以用到动态规划,我将leetcode上的大神原话翻译了一下。

1, 首先将这个问题分解成我们能编程解决的子问题。获胜的目标是获得最大的分数,所以我们需要最大化player1的分数总和,并检查其是否比player 2的分数总和大(或者比数组中所有数之和的1/2大)。无论怎样,我们可以意识到这是一个递归的问题,我们可以使用一步的状态来计算下一步的状态。

2, 然而,我们总是希望做得比递归更好。我们注意到,有很多多余的计算。比如,”player1拿最左元素,然后player2拿最左元素,然后player1拿最右元素,然后player2拿最右元素" 和 ”player1拿最右元素,然后player2拿最右元素,然后player1拿最左元素,然后player2拿最左元素"是一模一样的。所以,我们可以使用动态规划来保存中间状态。

3, 使用一个二维数组 dp[i][j] 来存储所有的中间状态。 dp中存储的是 how much more scores that the first-in-action player will earn from position i to j in the array.

4, 我们决定了 dp[i][j] 存储 “先手玩家” 从nums[i~j] 中比另一个玩家获得的更多的分数。 接下来一步是我们如何更新dp表从一个状态到另一个状态。让我们回到问题,每个玩家都可以选择拿数组中的最左或者最右数字,假设当前是player A的 turn 来从position i to j in the array中选择数字。如果A 拿了position i,那么 A 将立刻获得 nums[i] 分,然后 player B 将从 from i + 1 to j 中选择。而 dp[i + 1][j] 已经保存了 how much more score that the first-in-action player will get from i + 1 to j than the second player. 所以这意味着 player B 最终从 from i + 1 to j 获得 dp[i + 1][j]  (比A)更多的分。 因此,如果A拿了position i, 最终player A 将会获得 nums[i] - dp[i + 1][j] (比B)更多的分。类似的,如果A拿了 position j, player A 将会获得 nums[j] - dp[i][j - 1] (比B)更多的分. 因为A很聪明,那么A将会从这两个抉择中选一个获益最大的。所以:
dp[i][j] = Math.max(nums[i] - dp[i + 1][j], nums[j] - dp[i][j - 1]);

5, 现在我们有递归规则了,下一步是决定递归从哪里开始。这很简单,因为我们可以初始化 dp[i][i], 使得 dp[i][i] = nums[i]. 接下来的过程就是更新dp表。
让我们用一个 5 x 5 的dp表作为例子 , i是行数,j是列数. 首先我们来填充 dp[i][i], 这些都是对角线格子,我把它们标记为1。接着我们知道每个 dp[i][j] 的值都只依赖于 dp[i + 1][j] 和 dp[i][j - 1]. 从表中来看,这意味着每个 (i, j)格子只依赖于它的左边格子 (i, j - 1) 和下边格子 (i + 1, j). 所以在填充完标记为1的对角线格子后,我们可以开始计算被标记为2的格子。在那之后,我们又可以计算被标记为3的格子,等等等等。
0_1488092542752_dp.jpg在我的代码里,使用 len 来标记这个格子与对角线相距多远,所以 len 范围从 1 到 n - 1,这就是外层的循环。至于内层的循环,是所有有效的 i positions. 在填充完表中所有的右上角半边格子后, dp[0][n - 1]就是我们需要的结果。

public boolean PredictTheWinner(int[] nums) {    int n = nums.length;    int[][] dp = new int[n][n];    for (int i = 0; i < n; i++) { dp[i][i] = nums[i]; }    for (int len = 1; len < n; len++) {        for (int i = 0; i < n - len; i++) {            int j = i + len;            dp[i][j] = Math.max(nums[i] - dp[i + 1][j], nums[j] - dp[i][j - 1]);        }    }    return dp[0][n - 1] >= 0;}