很显然,这道题current situation depends on another (the next) step’s result,适合用dynamic programming解决,但找出dp公式并不简单。
因为我们有两个gamer,想知道第一个出手的gamer A能不能赢,则希望A每次出手都是在通向获得maximum score的道路上的最优选择;同时必须要假设第二个出手的gamer B肯定也是遵循同样的策略,希望每次出手都能在最终最大化地打击到A,使得A获得minimum score(关键点)。
在这里使用二维dp,dp[i][j]
的意义为:当score array中,还剩下index区间为[i,j]
的这些score没有被pick时,A能获得的maximum score;此时,index区间在[0,i-1]
和[j+1,n-1]
的score都已经被A或B pick走了。那么,A由此开始的第一轮只能pick i
或j
,如果选择i
,留给B pick的区间变为[i+1,j]
,如果选择j
,留给B pick的区间变为[i,j-1]
,则有:
dp[i][j] = Math.max(nums[i] + 「第二轮B pick i + 1 或 j后A在第三轮能获得的maximum score」, nums[j] + 「第二轮B pick i 或 j - 1后A在第三轮能获得的maximum score」)
两个「」中的部分怎么计算呢?那需要分情况来看:
第一轮A的选择 | 第二轮B的选择 | 第三轮A的maximum score |
---|---|---|
i | i+1 | dp[i+2][j] |
i | j | dp[i+1][j-1] |
j | i | dp[i+1][j-1] |
j | j-1 | dp[i][j-2] |
刚刚说过,B也会遵循同样的找最优策略,试图使A在第三轮获得的score最小,导致A在第三轮能获得的maximum score为Math.min(dp[i+2][j], dp[i+1][j-1])
或Math.min(dp[i+1][j-1], dp[i][j-2])
。则有最终公式:
dp[i][j] = Math.max(nums[i] + Math.min(dp[i+2][j], dp[i+1][j-1]), nums[j] + Math.min(dp[i+1][j-1], dp[i][j-2]))
代码如下:
public boolean PredictTheWinner(int[] nums) {
int n = nums.length;
if (n % 2 == 0) return true; //注
int[][] dp = new int[n][n];
int sum = 0;
for(int i = 0; i < n; i++) {
dp[i][i] = nums[i];
sum += nums[i];
}
for(int j = 1; j < n; j++) {
for(int i = j - 1; i >= 0; i--) {
int a = (i + 1 < n && j - 1 >= 0) ? dp[i + 1][j - 1] : 0;
int b = (i + 2 < n) ? dp[i + 2][j] : 0;
int c = (j - 2 >= 0) ? dp[i][j - 2] : 0;
dp[i][j] = Math.max(Math.min(a,b) + nums[i], Math.min(a,c) + nums[j]);
}
}
return dp[0][n-1] * 2 >= sum;
}
注:此处为优化,前提假设是两个gamer开始游戏之前能看到所有score。当score array长度为偶数时,index为奇数的score之和与index为偶数的score之和,必有相对较大的一个;而每次pick头或者尾的规则,能保证A在每一轮无论B的选择如何,都能pick和较大的index奇数组或index偶数组的所有元素(因为A首先pick或B pick完后,头或尾总是一奇一偶的),使得最终score之和比B大。