
题解:
区间dp,dpij表示从i,j范围内,先手获得的最大分数
第一层,先手可以从左边拿1个 + numsleft
第二层,后手可以拿从左边拿1个或者从右边拿1个,
第三层,先手从左边left+2,right left+1,right-1区间进行拿取,因为都是最聪明的所以这两种情况取的,最小值
同理第一层,先手可以从右边边拿1个 + numsright
第二层,后手可以拿从左边拿1个或者从右边拿1个,
第三层,先手从左边left+1,right-1 left,right-2区间进行拿取,因为都是最聪明的所以这两种情况取的,最小值
这两种方式的最大值当作先手的结果
区间动态规划问题, 具体定义状态的方式有很多种, 但是大同小异, 时空复杂度都相似. 这里只介绍 version 1 的具体实现.
设定 dpij 表示当前剩余硬币的区间为 i, j 时, 此时该拿硬币的人能获取的最大值.
注意, dpij 并没有包含角色信息, dp0values.length - 1 表示的是先手的人能获得的最大值, 而 dp1values.length -1 表示的则是后手的人能获得的最大值. 需要这样做是因为: 两个人都会采用最优策略.
当前的人的决策就是拿左边还是拿右边, 而下一个人仍然会最优决策, 所以应该是最小值中取最大值:
cpp
class Solution {
public:
bool predictTheWinner(vector<int>& nums) {
int len = nums.size();
if (len <= 0) {
return true;
}
int sum = 0;
for (auto num : nums) {
sum += num;
}
vector<vector<int>> memo(len, vector<int>(len, -1));
return sum <= 2 * dfs(0, len - 1, nums, memo);
}
int dfs(int left, int right, const vector<int>& nums,
vector<vector<int>>& memo) {
if (left == right) {
memo[left][right] = nums[left];
return nums[left];
}
if (left > right) {
return 0;
}
if (memo[left][right] != -1) {
return memo[left][right];
}
int pick_left = min(dfs(left + 1, right - 1, nums, memo),
dfs(left + 2, right, nums, memo)) +
nums[left];
int pick_right = min(dfs(left + 1, right - 1, nums, memo),
dfs(left, right - 2, nums, memo)) +
nums[right];
memo[left][right] = max(pick_left, pick_right);
return memo[left][right];
}
};
cpp
// 本题解信息来源于【九章算法】。请勿进行商业转载,非商业转载请注明出处。
// version 1
public class Solution {
/**
* @param values: an array of integers
* @return: a boolean which equals to true if the first player will win
*/
public boolean firstWillWin(int[] values) {
// write your code here
int n = values.length;
int[][] dp = new int[n + 1][n + 1];
boolean[][] flag = new boolean[n + 1][n + 1];
int sum = 0;
for (int now : values)
sum += now;
return sum < 2 * MemorySearch(0, values.length - 1, dp, flag, values);
}
int MemorySearch(int left, int right, int[][] dp, boolean[][] flag, int[] values) {
if (flag[left][right])
return dp[left][right];
flag[left][right] = true;
if (left > right) {
dp[left][right] = 0;
} else if (left == right) {
dp[left][right] = values[left];
} else if (left + 1 == right) {
dp[left][right] = Math.max(values[left], values[right]);
} else {
int pick_left = Math.min(MemorySearch(left + 2, right, dp, flag, values),
MemorySearch(left + 1, right - 1, dp, flag, values)) + values[left];
int pick_right = Math.min(MemorySearch(left, right - 2, dp, flag, values),
MemorySearch(left + 1, right - 1, dp, flag, values)) + values[right];
dp[left][right] = Math.max(pick_left, pick_right);
}
return dp[left][right];
}
}
// version 2
public class Solution {
/**
* @param values: an array of integers
* @return: a boolean which equals to true if the first player will win
*/
public boolean firstWillWin(int[] values) {
int n = values.length;
int[][] dp = new int[n + 1][n + 1];
boolean[][] flag = new boolean[n + 1][n + 1];
int[][] sum = new int[n + 1][n + 1];
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
sum[i][j] = i == j ? values[j] : sum[i][j - 1] + values[j];
}
}
int allsum = 0;
for (int now : values)
allsum += now;
return allsum < 2 * MemorySearch(0, values.length - 1, dp, flag, values, sum);
}
int MemorySearch(int left, int right, int[][] dp, boolean[][] flag, int[] values, int[][] sum) {
if (flag[left][right])
return dp[left][right];
flag[left][right] = true;
if (left > right) {
dp[left][right] = 0;
} else if (left == right) {
dp[left][right] = values[left];
} else if (left + 1 == right) {
dp[left][right] = Math.max(values[left], values[right]);
} else {
int cur = Math.min(MemorySearch(left + 1, right, dp, flag, values, sum),
MemorySearch(left, right - 1, dp, flag, values, sum));
dp[left][right] = sum[left][right] - cur;
}
return dp[left][right];
}
}
// 九章动态规划专题班和算法强化班版本
public class Solution {
/**
* @param values: an array of integers
* @return: a boolean which equals to true if the first player will win
*/
public boolean firstWillWin(int[] A) {
// write your code here
int n = A.length;
if (n == 0) {
return true;
}
int[][] f = new int[n][n];
int i, j, len;
// len 1
for (i = 0; i < n; ++i) {
f[i][i] = A[i];
}
for (len = 2; len <= n; ++len) {
for (i = 0; i <= n - len; ++i) {
j = i + len - 1;
f[i][j] = Math.max(A[i] - f[i + 1][j], A[j] - f[i][j - 1]);
}
}
return f[0][n - 1] >= 0;
}
}
// Linpz verision
public class Solution {
/**
* @param values: an array of integers
* @return: a boolean which equals to true if the first player will win
*/
public boolean firstWillWin(int[] values) {
int n = values.length;
int[] sum = new int[n + 1];
sum[0] = 0;
for (int i = 1; i <= n; ++i)
sum[i] = sum[i - 1] + values[i - 1];
// s[i][j] = sum[j + 1] - sum[i];
int[][] dp = new int[n][n];
for (int i = 0; i < n; ++i)
dp[i][i] = values[i];
for (int len = 2; len <= n; ++len) {
for (int i = 0; i < n; ++i) {
int j = i + len - 1;
if (j >= n)
continue;
int s = sum[j + 1] - sum[i];
dp[i][j] = Math.max(s - dp[i + 1][j], s - dp[i][j - 1]);
}
}
return dp[0][n - 1] > sum[n] / 2;
}
}