想要精通算法和SQL的成长之路 - 系列导航
主要思路:
dp[i][j]
:在区间 [i, j]
之间先手情况下能拿到的相对分数。[i, j]
之间,如果玩家1选择最左侧,值为num[i]
。那么玩家2只能在[i-1,j]
区间内选择,并且是先手。那么他能拿到的最大相对分数就是:dp[i+1][j]
。那么此时玩家1选择左手时的相对分数就是:num[i] - dp[i+1][j]
。num[j] - dp[i][j-1]
。Max(num[i] - dp[i+1][j], num[j] - dp[i][j-1])
。代码如下:
public boolean predictTheWinner(int[] nums) {
return dfs(0, nums.length - 1, nums) >= 0;
}
public int dfs(int left, int right, int[] nums) {
// 遍历完了,返回0
if (left > right) {
return 0;
}
// 选择最左侧时的最大相对差值
int chooseLeft = nums[left] - dfs(left + 1, right, nums);
// 选择最右侧时的最大相对差值
int chooseRight = nums[right] - dfs(left, right - 1, nums);
// 返回最大相对差值
return Math.max(chooseLeft, chooseRight);
}
当然,这类递归性质的代码,往往都存在一些重复计算的动作,我们用一个全局的数组,来记录递归过程中计算出来的值,即:记忆化搜索。
private int[][] memo;
public boolean predictTheWinner(int[] nums) {
int len = nums.length;
memo = new int[len][len];
// 初始化一个比较特殊的值,用于判断是否计算过
for (int i = 0; i < len; i++) {
Arrays.fill(memo[i], Integer.MAX_VALUE);
}
return dfs(0, nums.length - 1, nums) >= 0;
}
public int dfs(int left, int right, int[] nums) {
if (left > right) {
return 0;
}
// 记忆化搜索,如果搜索过,直接返回
if(memo[left][right] != Integer.MAX_VALUE) {
return memo[left][right];
}
// 如果当前先手,选择左边的数,那么后手就是:dfs(left + 1, right, nums),计算后手的最大值,我们求此时先后手的相对值
int chooseLeft = nums[left] - dfs(left + 1, right, nums);
int chooseRight = nums[right] - dfs(left, right - 1, nums);
return memo[left][right] = Math.max(chooseLeft, chooseRight);
}
也就是说不可能存在平局的情况。
在满足上述两个条件的基础上:先手必胜。
我们假设一个数组如下:[奇, 偶, 奇, 偶, 奇, 偶, 奇, 偶, 奇, 偶, 奇, 偶]。
奇
, 偶, 奇, 偶, 奇, 偶, 奇, 偶, 奇, 偶, 奇, 偶
]。偶
, 奇, 偶, 奇, 偶, 奇, 偶, 奇, 偶, 奇, 偶
]。反之同理,只能选择奇奇序列。总之就是:先手必定是奇偶性不同的局面。后手必定是奇偶性相同的局面。
那么问题简单了,我们只需要知道,奇序列的总和 和 偶序列总和 谁大,然后先手每次决策的时候,限制对方只能选择奇偶序列的对立面即可。
因此题目中既然说明了Alice
先手的情况,我们直接返回true
就完事了。
public boolean stoneGame(int[] piles) {
return true;
}