你和你的朋友面前有一排石头堆,用一个数组piles表示,piles[i] 表示第 i 堆石子有多少个。你们轮流拿石头,一次拿一堆,但是只能拿走最左边或者最右边的石头堆。所有石头被拿完后,谁拥有的石头多,谁获胜。
石头的堆数可以是任意正整数,石头的总数也可以是任意正整数,这样就能打破先手必胜的局面了。比如有三堆石头 piles = {1, 100, 3},先手不管拿1还是拿3,能够决定胜负的 100 都会被后手拿走,后手会获胜。
假设两人都很聪明,请你设计一个算法,返回先手和后手的最后得分(石头总数)之差。比如上面的那个例子,先手能获得 4 分,后手会获得 100 分,你的算法应该返回 -96。
1、确定dp数组及其下标的含义
dp数组是一个Pair类型的数组,里面包含 first 和 second 两个属性,简写为 fir 和 sec。其实本题也可以用一个三维数组 dp[n][n][2]。
注:由上述dp数组定义可以看出,j 是大于等于 i 的。因此第二层for循环的 j 是从 j = i 开始的。
2、确定递推公式
3、dp数组如何初始化
4、确定遍历顺序
由递推公式可以看出,要求出 dp[i][j] 需要先求出 dp[i + 1][j],因此 i 需要从下向上遍历;要求出 dp[i][j] 需要先求出 dp[i][j - 1],因此 j 需要从左向右遍历。
5、举例推导dp数组
public class BoYi{
public static void main(String[] args) {
int[] piles = {3, 9, 1, 2};
int n = piles.length;
/* dp[i][j].first表示,对于piles[i...j]这部分石头堆,先手能获得的最高分数。
dp[i][j].second表示,对于piles[i...j]这部分石头堆,后手能获得的最高分数。
其实直接用一个三维数组dp[n][n][2]也可以。*/
Pair[][] dp = new Pair[n][n];
// 初始化dp数组
for(int i = 0; i < n; i++){
for(int j = 0; j < n; j++){
dp[i][j] = new Pair(0, 0);
}
}
// 填入base case。只有一堆石头,显然先手得分为piles[i],后手的得分为0
for(int i = 0; i < n; i++){
dp[i][i].first = piles[i];
dp[i][i].second = 0;
}
for(int i = n - 1; i >= 0; i--){
for(int j = i; j < n; j++){
if(i == j){
continue;
}
int left = piles[i] + dp[i + 1][j].second;
int right = piles[j] + dp[i][j - 1].second;
if(left > right){
dp[i][j].first = left;
dp[i][j].second = dp[i + 1][j].first;
}else {
dp[i][j].first = right;
dp[i][j].second = dp[i][j - 1].first;
}
}
}
Pair res = dp[0][n - 1];
System.out.println(res.first - res.second);
}
}
class Pair {
int first;
int second;
public Pair(int first, int second) {
this.first = first;
this.second = second;
}
}