思考过程:
假如使用穷举,时间复杂度是2^n,题目说到n最大为20,似乎可行。按此实现代码。
代码实现:
public boolean PredictTheWinner1(int[] nums) {
int left = 0, right = nums.length - 1;
int sum1 = 0, sum2 = 0;
return calc(nums, sum1, sum2, left, right, 0);
}
private boolean calc(int[] nums, int sum1, int sum2, int left, int right, int index) {
if (left > right) {
return sum1 >= sum2;
}
if (index % 2 == 0) {//玩家1
return calc(nums, sum1 + nums[left], sum2, left + 1, right, index + 1)
|| calc(nums, sum1 + nums[right], sum2, left, right - 1, index + 1);
} else {
return calc(nums, sum1, sum2 + nums[left], left + 1, right, index + 1)
|| calc(nums, sum1, sum2 + nums[right], left, right - 1, index + 1);
}
}
然后发现执行不通过,再看题目,每个玩家都要使自己的分数最大化,如果穷举,显然不能满足。所以这个思路行不通。
按照方法一的思路,实现了如下代码(也有借鉴官方代码):
public boolean PredictTheWinner(int[] nums) {
int left = 0, right = nums.length - 1;
return total(nums, left, right, 1) >= 0;
}
/**
* 当玩家1时,+当前值,当玩家2时,-当前值
* 然后判断最终的值是否大于大于0
* 玩家1希望这个值越大越好,大于等于0就赢了
* 玩家2希望这个值越小越好,小于0就赢了
*/
private int total(int[] nums, int left, int right, int turn) {
//当左右指针相等,表示只剩一个值,如果当前是玩家1,返回正,是玩家2返回负
if (left == right) {
return nums[left] * turn;
}
//当前是玩家1,分为两种情况,取左值和取右值,然后取两种情况中的最大值
if (turn > 0) {
int leftTotal = nums[left] + total(nums, left + 1, right, -turn);
int rightTotal = nums[right] + total(nums, left, right - 1, -turn);
return Math.max(leftTotal, rightTotal);
} else {
//当前是玩家2,也是分两种情况,不同的是取两种情况的最小值,因为玩家2希望的是值越小越好
int leftTotal = -nums[left] + total(nums, left + 1, right, -turn);
int rightTotal = -nums[right] + total(nums, left, right - 1, -turn);
return Math.min(leftTotal, rightTotal);
}
}
时间复杂度O(2^n)
方法一中,存在大量重复计算,可以用map保存值,减少重复计算,也可以使用动态规划的解法,方法二就是动态规划。
使用map保存计算值的代码实现:
public boolean PredictTheWinner2(int[] nums) {
int left = 0, right = nums.length - 1;
Map<String, Integer> map = new HashMap<>();
return total(nums, left, right, 1, map) >= 0;
}
/**
* 当玩家1时,+当前值,当玩家2时,-当前值
* 然后判断最终的值是否大于大于0
* 玩家1希望这个值越大越好,大于等于0就赢了
* 玩家2希望这个值越小越好,小于0就赢了
*/
private int total(int[] nums, int left, int right, int turn, Map<String, Integer> map) {
String s = left + "" + turn + "" + right;
if (map.containsKey(s)) {
return map.get(s);
}
//当左右指针相等,表示只剩一个值,如果当前是玩家1,返回正,是玩家2返回负
if (left == right) {
return nums[left] * turn;
}
//当前是玩家1,分为两种情况,取左值和取右值,然后取两种情况中的最大值
if (turn > 0) {
int leftTotal = nums[left] + total(nums, left + 1, right, -turn, map);
int rightTotal = nums[right] + total(nums, left, right - 1, -turn, map);
int ans = Math.max(leftTotal, rightTotal);
map.put(s, ans);
return ans;
} else {
//当前是玩家2,也是分两种情况,不同的是取两种情况的最小值,因为玩家2希望的是值越小越好
int leftTotal = -nums[left] + total(nums, left + 1, right, -turn, map);
int rightTotal = -nums[right] + total(nums, left, right - 1, -turn, map);
int ans = Math.min(leftTotal, rightTotal);
map.put(s, ans);
return ans;
}
}
性能稍好。
动态规划代码实现:(官方实现)
public boolean PredictTheWinner(int[] nums) {
int length = nums.length;
int[] dp = new int[length];
for (int i = 0; i < length; i++) {
dp[i] = nums[i];
}
for (int i = length - 2; i >= 0; i--) {
for (int j = i + 1; j < length; j++) {
dp[j] = Math.max(nums[i] - dp[j], nums[j] - dp[j - 1]);
}
}
return dp[length - 1] >= 0;
}
性能最佳,且代码最优雅。
总结:
这一类问题,一般有三种解法,1.递归;2.递归的基础上,保存计算过的值,减少重复计算;3动态规划。
其中最优解法是动态规划,动态规划的难点在于,找到状态转移方程。