[LeetCode] 486. Predict the Winner 预测赢家

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: False
Explanation: 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: True
Explanation: 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.

给定一个正整数数组,选手1从数组的头部或者尾部选择一个数,选手2从剩下部分的头部或尾部选择一个数,循环往复,直到该数组中的数都被取完。判断选手1取的数的和值是否大于选手2.  

一个典型的Minimax算法的应用,两个玩家都要使自己的利益最大化,使对手的利益最小化。Minimax是一个零和博弈中的算法。而Alpha-beta剪枝则是一个基于Minimax的剪枝算法(当算法评估出某策略的后续走法比之前策略的还差时,就会停止计算该策略的后续发展)。

两人依次拿,如果Player1赢,则Player1拿的>Player2拿的。把Player1拿的视为"+",把Player2拿的视为"-",如果最后结果大于等于0则Player1赢。

因此对于递归来说,beg ~ end的结果为max(nums[beg] - partition(beg + 1, end), nums[end] - partition(beg, end + 1));对于非递归来说DP[beg][end]表示即为beg ~ end所取的值的大小(最终与零比较)。

总结:

1. 该问题没有直接比较一个选手所拿元素的和值,而是把问题转换为两个选手所拿元素的差值。这一点很巧妙,是关键的一步。

2. 找出递推表达式:max(nums[beg] - partition(beg + 1, end), nums[end] - partition(beg, end + 1))

3. 通过递推表达式构造递归算法是比较简单的。但是要构造一个非递归的算法难度较大。对于非递归算法,首先在dp中赋初始值,这是解题的第一步。在这个问题中,使用一个二位的数组dp来表示nums数组中任意开始和结束位置两人结果的差值。

初始值仅仅知道对角线上的值,dp[i][i] = nums[i].

接下来既然是求任意的开始和结束,对于二维数组,那肯定是一个双层的循环。通过dp中已知的元素和动态规划的递推表达式,就可以构造出需要的结果。非递归的方式是从小问题到大问题的过程。

解法1: 递归。

解法2: DP, 比较难想公式。建立dp[len][len]数组,dp[i][j]表示nums[i, j]下标间玩家1能够获得的分数减去玩家2能够获得的最大分数差,最后看dp[0][len-1]的正负来判断1是否能赢。

先看dp[i][j]存储玩家1能从任意子数组[i,j]中获得的最大分数的情况:

给定一个数组nums[i,j],玩家1能够拿nums[i]或者nums[j], 剩下一个新的数组nums[i+1, j]或者nums[i, j-1],然后轮到玩家2拿,因为是博弈游戏,所以玩家2能拿到的最大数值是玩家1能拿到的最大数值。
DP公式:

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

= max((sum(i, j) - dp(i, j-1)), (sum(i, j) - dp(i-1, j))) = sum(i, j) - min(dp(i-1, j), dp(i, j-1))

可以将sum(i,j)从公式中消除,只存储玩家1和玩家2的最大差值,比如玩家1得到A,玩家2得到B,用dp来存储A-B,如果A=dp(i, j),那么B=sum(i, j) - dp(i, j)。

因此dp'(i, j) = dp(i, j) - ( sum(i, j) - dp(i, j) ) = 2*dp(i, j) - sum(i, j), 所以2*dp(i, j) = dp'(i, j) + sum(i, j) 

dp'(i, j) = dp(i, j) - ( sum(i, j) - dp(i, j) )
= 2dp(i, j) - sum(i, j)
= 2 * max( sum(i, j) - dp(i, j-1), sum(i, j) - dp(i+1, j) ) - sum(i, j)
= max(sum(i, j) - 2*dp(i, j-1), sum(i, j) - 2*dp(i+1, j) )
= max(sum(i, j) - ( dp'(i, j-1) + sum(i, j-1) ), sum(i, j) - ( dp'(i+1, j) + sum(i+1, j)))
= max(sum(i, j) - sum(i, j-1) - dp'(i, j-1), sum(i, j) - sum(i+1, j) - dp'(i+1, j))
= max(nums[j] - dp'(i, j-1), nums[i] - dp'(i+1, j))

最后的公式:  dp(i, j) = max(nums[j] - dp(i, j-1), nums[i] - dp(i+1, j))

Java: DP

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;
}

Java: DP, O(n) space

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

Java:

public class Solution {
    public boolean PredictTheWinner(int[] nums) {
        return findAns(nums, 0, nums.length-1) >= 0;
    }

    public int findAns(int[] nums, int i, int j) {
        if (i == j) return nums[i];
        else {
            int first = nums[i] - findAns(nums, i+1, j);
            int last = nums[j] - findAns(nums, i, j-1);
            return Math.max(first, last);
        }
    }
}

Java: Recursion,不保存中间状态

public class Solution {
    public boolean PredictTheWinner(int[] nums) {
        return helper(nums, 0, nums.length-1) >= 0;
    }
    
    public int helper(int[] nums, int start, int end) {
        if(start == end) return nums[start];
        else return Math.max(nums[start]-helper(nums, start+1,end), nums[end]-helper(nums, start,end-1));
    }
}

Java: Recursion. 保存中间状态

public class Solution {
    public boolean PredictTheWinner(int[] nums) {
        return helper(nums, 0, nums.length-1, new Integer[nums.length][nums.length]) >= 0;
    }
    
    public int helper(int[] nums, int start, int end, Integer[][] dp) {
        if(dp[start][end] == null) {
            if(start == end) return nums[start];
            else return Math.max(nums[start]-helper(nums, start+1,end, dp), nums[end]-helper(nums, start,end-1, dp));
        }
        return dp[start][end];
    }
} 

Java:DP

public class Solution {
    public boolean PredictTheWinner(int[] nums) {
        if(nums == null || nums.length == 0) return false;
        int n = nums.length;
        int[][] dp = new int[n][n];
        
        for(int i = 0; i < n; i++) {
            dp[i][i] = nums[i];
        }
        
        for(int i = n-2; i >= 0; i--) {
            for(int j = i+1; j < n; j++) {
                dp[i][j] = Math.max(nums[i]-dp[i+1][j], nums[j]-dp[i][j-1]);
            }
        }
        
        return dp[0][n-1] >= 0;
    }
}

Python:

class Solution(object):
    def predictTheWinner(self, nums):
        
        return self.helper(nums, 0, len(nums) - 1) >= 0
        
        
    def helper(self, nums, start, end):
        if start == end:
            return nums[start]
        
        first = nums[start] - self.helper(nums, start + 1, end)
        last = nums[end] - self.helper(nums, start, end - 1)

        return max(first, last)  

Python:

class Solution(object):
    def PredictTheWinner(self, nums):
        """
        :type nums: List[int]
        :rtype: bool
        """
        cache = dict()
        def solve(nums):
            if len(nums)<=1:
                return sum(nums)
            tnums = tuple(nums)
            if tnums in cache:
                return cache[tnums]
            cache[tnums] = max(nums[0]-solve(nums[1:]), nums[-1]-solve(nums[:-1]))
            return cache[tnums]
        return solve(nums)>=0

C++:

class Solution {
public:
    bool PredictTheWinner(vector<int>& nums) {
        return helper(nums, 0, nums.size()-1)>=0;
    }
private:
    int helper(vector<int>& nums, int s, int e){        
        return s==e ? nums[e] : max(nums[e] - helper(nums, s, e-1), nums[s] - helper(nums, s+1, e));
    }
};

  

  

  

类似题目:

[LeetCode] 375. Guess Number Higher or Lower II 猜数字大小 II

[Google] 9717 取数对弈

 

All LeetCode Questions List 题目汇总

 

 

 

 

 

posted @ 2018-03-24 07:33  轻风舞动  阅读(713)  评论(0编辑  收藏  举报