Leetcode_486_PredictTheWinner_877_StoneGame_Medium
/**
* Leetcode486与Leetcode877说明:
* 两题目解法几乎一样,可以看做一个题目,唯一不同的是877必需是差值大于0,二486只需要大于等于0即可。
* 题目区别:486的nums长度可为奇数或偶数,且总和可以为偶数也可以为奇数。
* 877对应的两人选择次数相同,总和肯定不同,即最终差值肯定不为0;
* 而486对应的两人选择次数可能不相同,有一人可能多选择一个,最终差值可能为0.
* 先介绍Leetcode877题目,仿照Leetcode486即可解。
*/
1、 Leetcode_877_StoneGame
(1)、问题描述
/***********Leetcode_877_StoneGame_Medium*******/
/**
* Leetcode_877_StoneGame_Medium
* Alex and Lee play a game with piles of stones. There are an even number of piles
* arranged in a row, and each pile has a positive integer number of stones piles[i].
* The objective of the game is to end with the most stones. The total number of stones is odd,
* so there are no ties.
* Alex and Lee take turns, with Alex starting first. Each turn, a player takes
* the entire pile of stones from either the beginning or the end of the row.
* This continues until there are no more piles left, at which point the person with the most stones wins.
* Assuming Alex and Lee play optimally, return True if and only if Alex wins the game.
* <p>
* Example 1:
* Input: [5,3,4,5]
* Output: true
* Explanation:
* Alex starts first, and can only take the first 5 or the last 5.
* Say he takes the first 5, so that the row becomes [3, 4, 5].
* If Lee takes 3, then the board is [4, 5], and Alex takes 5 to win with 10 points.
* If Lee takes the last 5, then the board is [3, 4], and Alex takes 4 to win with 9 points.
* This demonstrated that taking the first 5 was a winning move for Alex, so we return true.
* Note:
* 2 <= piles.length <= 500
* piles.length is even.
* 1 <= piles[i] <= 500
* sum(piles) is odd.
* <p>
(2)、思路分析:
* 方法0:直接返回true即可。两人都是以最佳方式选择,且堆数为偶数,总和为奇数,因此谁先选择谁赢。
* 方法1:利用Min-Max递归解法,返回最大的相对差值。
* 时间复杂度为O(N^2),无法通过。
* 方法2:利用动态规划解决。dp[][]
* dp[start][end]表示start到end最佳选择的最大差值。
* 实际利用的是dp[][]的右上三角;按照对角线遍历,利用perLen来进行遍历填值。
* 首先最长对角线初始化为piles值;然后递推公式:
* dp[start][end] = Math.max(piles[start] - dp[start + 1][end], piles[end] - dp[start][end - 1]);
* 时间复杂度:O(N^2);
* 空间复杂度:O(N).
* 方法3:利用动态规划解决,降低空间复杂度。dp[N]
* 从递推公式dp[start][end] = Math.max(piles[start] - dp[start + 1][end], piles[end] - dp[start][end - 1])
* 看出dp[start][end]只与相邻左边元素和下边元素有关,即上一个对角线上的两个元素,
* 每次perLen循环就是从旧的对角线更新到新的对角线,且对角线长度逐渐降低,因此元素覆盖即可。
*/
(3)Java代码
// 方法0:直接返回true即可,Alex必定会赢
public boolean stoneGame1(int[] piles) {
if (piles == null || piles.length == 0) return false;
return true;
}
/**
* 方法1:Min-Max方法:当piles长度过大时,递归时间太长
* Leetcode提交超时不通过Time Limit Exceeded
* 时间复杂度:O(2^N),太大了
* 空间复杂度:O(N);递归深度为N
*/
public boolean stoneGame_Recursive(int[] piles) {
if (piles == null || piles.length == 0) return false;
return relativeMaxDiff(piles, 0, piles.length - 1) > 0;
}
/***
* 由于两人都是最佳选择:所以抽象一个方法,从start到end时选择相对差值最大的。
*
* 数组从start到end最佳选择下返回的最大差值
* 递归终止条件:start==end返回本身即可;
* 否则,对应两种选择:1、自己选择start,然后减去对手从start+1到end的最佳选择最大差值;
* 2、自己选择end,然后减去对手从start到end-1的最佳选择的最大差值。
* 两种选择中取max,即自身选择从start到end的最大差值。
*/
public int relativeMaxDiff(int[] piles, int start, int end) {
if (start < 0 || end >= piles.length || start > end) return 0;//边界条件判断
if (start == end) return piles[start];//递归终止条件
return Math.max(piles[start] - relativeMaxDiff(piles, start + 1, end),
piles[end] - relativeMaxDiff(piles, start, end - 1));
}
/**
* 方法2:Min-Max方法+DP:由于递归存在大量重复问题,利用dp[start][end]解决重复问题
* 时间复杂度:O(N^2)
* 空间复杂度:O(N^2);仅仅利用矩阵dp[][]的右上角。
*/
public boolean stoneGame_DP1(int[] piles) {
if (piles == null || piles.length == 0) return false;
int len = piles.length;
int[][] dp = new int[len][len];
//初始化
for (int i = 0; i < len; i++) dp[i][i] = piles[i];
int end;
for (int perLen = 2; perLen <= len; perLen++) {
for (int start = 0; start <= len - perLen; start++) {
end = start + perLen - 1;
dp[start][end] = Math.max(piles[start] - dp[start + 1][end], piles[end] - dp[start][end - 1]);
}
}
return dp[0][len - 1] > 0;//大于0,获胜
}
/**
* 方法3:动态规划解法,降低空间复杂度
* 时间复杂度:O(N^2)
* 空间复杂度:O(N);仅仅利用矩阵dp[][]的反对角线。
*/
public boolean stoneGame(int[] piles) {//最佳
if (piles == null || piles.length == 0) return false;
int len = piles.length;
int[] dp = new int[len];//相当于dp[][]的反对角线
//初始化
for (int i = 0; i < len; i++) dp[i] = piles[i];
int end;
for (int perLen = 2; perLen <= len; perLen++) {
for (int start = 0; start <= len - perLen; start++) {
end = start + perLen - 1;
dp[start] = Math.max(piles[start] - dp[start + 1], piles[end] - dp[start]);
}
}
return dp[0] > 0;
}
2、Leetcode_486_PredictTheWinner_Medium
(1)、问题描述
/**
* Leetcode_486_PredictTheWinner_Medium
* 难度:Medium
* 类型:Dame,DP动态规划
* 问题介绍:
* 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 <= length of the array <= 20.
* Any scores in the given array are non-negative integers and will not exceed 10,000,000.
* If the scores of both players are equal, then player 1 is still the winner.
* <p>
(2)、思路分析
* 思路分析:
* 与877区别:返回的差值为0,也表示Player 1能赢。
* 方法1:递归方法;
* 方法2:动态规划,dp[len][len];
* 方法3:动态规划,dp[len].
(3)、Java代码
public boolean PredictTheWinner_Recursive(int[] nums) {
if (nums == null || nums.length == 0) return false;
return relativeMaxDiff_1(nums, 0, nums.length - 1) >= 0;
}
public int relativeMaxDiff_1(int[] nums, int start, int end) {
if (start < 0 || end >= nums.length || start > end) return 0;//边界条件判断
if (start == end) return nums[start];//递归终止条件
return Math.max(nums[start] - relativeMaxDiff_1(nums, start + 1, end),
nums[end] - relativeMaxDiff_1(nums, start, end - 1));
}
/***
* 方法2:动态规划,dp[len][len]
* 具体介绍看Leetcode877.
*/
public boolean PredictTheWinner_DP1(int[] piles) {
if (piles == null || piles.length == 0) return false;
int len = piles.length;
int[][] dp = new int[len][len];
//初始化
for (int i = 0; i < len; i++) dp[i][i] = piles[i];
int end;
for (int perLen = 2; perLen <= len; perLen++) {
for (int start = 0; start <= len - perLen; start++) {
end = start + perLen - 1;
dp[start][end] = Math.max(piles[start] - dp[start + 1][end], piles[end] - dp[start][end - 1]);
}
}
return dp[0][len - 1] >= 0;//大于0,获胜
}
/**
* 方法3:动态规划解法,降低空间复杂度
* 时间复杂度:O(N^2)
* 空间复杂度:O(N);仅仅利用矩阵dp[][]的反对角线。
*/
public boolean PredictTheWinner(int[] nums) {//最佳
if (nums == null || nums.length == 0) return false;
int len = nums.length;
int[] dp = new int[len];//相当于dp[][]的反对角线
//初始化
for (int i = 0; i < len; i++) dp[i] = nums[i];
int end;
for (int perLen = 2; perLen <= len; perLen++) {
for (int start = 0; start <= len - perLen; start++) {
end = start + perLen - 1;
dp[start] = Math.max(nums[start] - dp[start + 1], nums[end] - dp[start]);
}
}
return dp[0] > 0;
}