题目描述
分析
这道题是比较有技巧性的。由于题目规定石子堆数为偶数,那么可以把石子堆分成两种堆——奇数堆和偶数堆,先开始的人可以分别计算这两种堆的总和,如果奇数堆的和大,那么就可以一直选择奇数堆的石子堆(为什么可以一直选择奇数堆的石子呢?因为堆数为偶数,两端的堆就分别属于奇数堆和偶数堆,先开始的人拿走一堆后,两端的堆就只属于奇数堆或只属于偶数堆了,后开始的人拿走一堆后,先开始的人又可以选择拿奇数的堆或偶数的堆了),反之亦然,所以谁先开始,谁就能胜。这题Alex先开始,所以可以直接返回true。
用动态规划可以解决更一般化的问题,如石子堆数不一定是偶数,不仅要判断胜负,还要计算得分等等。那么子问题是什么呢?我们可以先计算出连续石子堆总数为1的胜负情况(Alex一定胜对吧!此时Alex最终比Lee多得到的石子数就等于这个石子堆的石子数,我们把它称为分数),然后计算连续石子堆总数为2的情况(Alex同样可以很轻松地选择总数大的那一堆,Alex的分数也很容易计算),接着计算连续堆总数为3的情况(得思考一下了,Alex可以选择第一堆或者第三堆,选完之后,就还剩下两堆了,这时Lee同样也会做出最佳选择,也就是说,Lee的暂时得分可以通过前面计算出的,连续石子堆总数为2的情况得出!那么Alex的得分,就等于Alex选出的那一堆的石子数,减去Lee的暂时得分),以此类推,可以算出连续堆总数为4、5、6……n-1的情况,最后就能算出连续堆总数为n的情况啦。
我们用dp[ i ][ j ]表示连续石子堆为原石子堆中第i堆到第j堆(包含i,j)时,先开始的人的得分。当只有一堆时,也就是i = j时,dp[ i ][ j ] 就等于piles[ i ],当有n堆时,dp[ i ][ j ]可以由n-1堆时的先手得分算出,选最左边的堆时,得分为piles[ i ] - dp[ i + 1 ][ j ],选最右边的堆时,得分为piles[ j ] - dp[ i ][ j - 1 ], dp[ i ][ j ] 取两者中分数大者。
C++代码
class Solution {
public:
bool stoneGame(vector<int>& piles) {
int l = piles.size();
int dp[l][l] = {};
for(int i = 0; i < l; i++) dp[i][i] = piles[i];
for(int n = 1; n < l; n++)
for(int i = 0; i < l - n; i++)
dp[i][i + n] = max(piles[i] - dp[i + 1][i + n], piles[i + n] - dp[i][i + n - 1]);
return dp[0][l - 1] > 0;
}
};