亚历克斯和李用几堆石子在做游戏。偶数堆石子排成一行,每堆都有正整数颗石子 piles[i] 。 游戏以谁手中的石子最多来决出胜负。石子的总数是奇数,所以没有平局。 亚历克斯和李轮流进行,亚历克斯先开始。 每回合,玩家从行的开始或结束处取走整堆石头。 这种情况一直持续到没有更多的石子堆为止,此时手中石子最多的玩家获胜。 假设亚历克斯和李都发挥出最佳水平,当亚历克斯赢得比赛时返回 true ,当李赢得比赛时返回 false 。
示例:
输入:[5,3,4,5]
输出:true
解释: 亚历克斯先开始,只能拿前 5 颗或后 5 颗石子 。 假设他取了前 5 颗,这一行就变成了 [3,4,5] 。 如果李拿走前 3 颗,那么剩下的是 [4,5],亚历克斯拿走后 5 颗赢得 10 分。 如果李拿走后 5 颗,那么剩下的是 [3,4],亚历克斯拿走后 4 颗赢得 9 分。 这表明,取前 5 颗石子对亚历克斯来说是一个胜利的举动,所以我们返回 true 。
链接:https://leetcode-cn.com/problems/stone-game
【思路】
1.dp问题。首先,每次有两种取法,要么选最前一堆石子,要么取最后一堆石子。由于这里涉及到两个先后对象,因此需要三维数组来表示每次的状态,从编号为i堆石子到编号为j堆石子,dp[i][j][0]表示先拿者,dp[i][j][1]表示后拿者。
2.在每次取过之后需要保证最优情况,用left表示取最前一堆石子,right表示取最后一堆石子。两种情况选值大的一种。这里有状态方程:dp[i][j][0] = max(piles[i] + dp[i+1][j][1], piles[j] + dp[i][j-1][1]);其中,left=piles[i] + dp[i+1][j][1],right=piles[j] + dp[i][j-1][1]。
上述状态方程中,我们考虑从编号i堆石子到j堆石子中,先拿者要选取最优值的情况下,后拿者在此基础上选最优值。因此,
- 如果先拿者拿了最前一堆石子,那么后拿者dp[i][j][1]为[i+1,j]中选取最优值,并且此时后拿者具有优先权,即dp[i+1][j][0]。
- 如果先拿者拿了最后一堆石子,那么后拿者dp[i][j][1]为[i,j+1]中选取最优值,并且此时后拿者具有优先权,即dp[i][j-1][0]。
最终判断dp[0][pilesSize-1][0]和dp[0][pilesSize-1][1]中那个值大就知道谁拿的石子多,谁获胜。
这里需要注意到一点是这个数组在dp的时候并不是逐行赋值的,由于dp[i][j][0] = max(piles[i] + dp[i+1][j][1], piles[j] + dp[i][j-1][1]);那么在赋值i,j时需要知道i+1,j和i,j-1的值了。简单画图可以知道是按照对角线赋值的。那么整个递归方式如下代码所示。
bool stoneGame(int* piles, int pilesSize){
int i,j,l;
int dp[pilesSize][pilesSize][2];
int left,right;
for(i=0;i<pilesSize;i++){
dp[i][i][0]=piles[i];
dp[i][i][1]=0;
}
for(l=1;l<pilesSize;l++){
for(i=0;i<pilesSize-l;i++){
j=i+l;
left=piles[i]+dp[i+1][j][1];
right=piles[j]+dp[i][j-1][1];
if(left>right){//选择值大的一种情况,对于特定的i,j需要更新以下两个变量
dp[i][j][0]=left;
dp[i][j][1]=dp[i+1][j][0];
}
else{
dp[i][j][0]=right;
dp[i][j][1]=dp[i][j-1][0];
}
}
}
if(dp[0][pilesSize-1][0]>dp[0][pilesSize-1][1])//如果先选的人最终值大于后选者,返回true
return true;
else
return false;
}