题目描述
亚历克斯和李用几堆石子在做游戏。偶数堆石子排成一行,每堆都有正整数颗石子 piles[i]
。
游戏以谁手中的石子最多来决出胜负。石子的总数是奇数,所以没有平局。
亚历克斯和李轮流进行,亚历克斯先开始。 每回合,玩家从行的开始或结束处取走整堆石头。 这种情况一直持续到没有更多的石子堆为止,此时手中石子最多的玩家获胜。
假设亚历克斯和李都发挥出最佳水平,当亚历克斯赢得比赛时返回 true
,当李赢得比赛时返回 false
。
示例:
1 | 输入:[5,3,4,5] |
2 <= piles.length <= 500
piles.length
是偶数。1 <= piles[i] <= 500
sum(piles)
是奇数。
题解
动态规划
介绍 dp 数组的含义之前,我们先看一下 dp 数组最终的样子:
以下是对 dp 数组含义的解释:
1 | dp[i][j].fir 表示,对于 piles[i...j] 这部分石头堆,先手能获得的最高分数。 |
写状态转移方程很简单,首先要找到所有「状态」和每个状态可以做的「选择」,然后择优。
根据前面对 dp 数组的定义,状态显然有三个:开始的索引 i,结束的索引 j,当前轮到的人。
对于这个问题的每个状态,可以做的选择有两个:选择最左边的那堆石头,或者选择最右边的那堆石头。 我们可以这样穷举所有状态:
1 | n = piles.length |
这道题的难点在于,两人是交替进行选择的,也就是说先手的选择会对后手有影响,这怎么表达出来呢?
根据我们对 dp 数组的定义,很容易解决这个难点,写出状态转移方程:
1 | dp[i][j].fir = max(piles[i] + dp[i+1][j].sec, piles[j] + dp[i][j-1].sec) |
根据 dp 数组的定义,我们也可以找出 base case,也就是最简单的情况
1 | dp[i][j].fir = piles[i] |
这里需要注意一点,我们发现 base case 是斜着的,而且我们推算 dp[i][j] 时需要用到 dp[i+1][j]
和dp[i][j-1]
:
所以说算法不能简单的一行一行遍历 dp 数组,而要斜着遍历数组:
1 | class Couple { |
为了降低时间和空间复杂度, 可以不定义一个类来表示先手和后手一对元素, 而是采用三维数组的方式:
1 | dp[i][j][0]表示先手获得的总数 |