题目简述:给定非负数组nums。有两个玩家,从玩家1开始交替取出一个数并加到他的分数上,取的时候只能取数组剩余的头尾两个数之一。当所有数都被取完时分数高的玩家获胜。求在给定nums的情况下,预测玩家1是否获胜。假设两个玩家每次都会做出最优选择。
输入:非负数组nums。
输出:一个bool值,表示玩家1是否获胜。
示例:对于数组[1,5,2],玩家1必输,因为无论取1还是2,玩家2都会取5而获胜。
题解:
这是一个最大最小策略的博弈问题。考虑使用动态规则求在某个状态下所能获得的分数最大值。记
sum(x,y)=∑yi=xnums[i]
表示区间[x,y]的数之和,
dp_max(x,y)
、
dp_min(x,y)
表示在区间[x,y]通过最优策略所能获得的最大分数和最小分数。
状态转移方程是:
dp_max(i,j)=max(xi+dp_min(i+1,j),xj+dp_min(i,j−1))
简单解释一下,对于区间[i,j],都是两种取法,那么就取这两个方法中更高分数的那个。一旦取了一个数,由于对手也会做最优选择,因此你在剩余的区间得到的分数肯定是最小的。
然后根据 sum(x,y)=dp_max(x,y)+dp_min(x,y) ,可以对状态转移方程进行变形,使其不出现 dp_min(x,y) :
dp_max(i,j)=max(xi+dp_min(i+1,j),xj+dp_min(i,j−1))=max(xi+sum(i+1,j)−dp_max(i+1,j),xj+sum(i,j−1)−dp_max(i,j−1))=max(sum(i,j)−dp_max(i+1,j),sum(i,j)−dp_max(i,j−1))=sum(i,j)−min(dp_max(i+1,j),dp_max(i,j−1))
初始状态是 dp_max(i,i)=nums[i]
这个状态转移方程显然可以用递推实现,先从区间长度为1的 dp_max(i,i) 出发,求出区间长度为2的 dp_max(i,i+1) ,依次增加区间长度,直至求目标状态 dp_max(0,n−1) ,表示玩家1所能获得的分数最大值,如果它大于或等于总分的一半,则玩家1必胜。
最后进行空间上的优化,注意到求出
dp_max(i,j)
只需要它上一级的
dp_max(i,j−1)
以及相邻的
dp_max(i+1,j)
,因此可以改二维为一维,只需要求dp的时候从左到右计算即可。新的状态转移方程是:
dp_max(i)=sum(i,i+j)−min(dp(i),dp(i+1)),其中j为区间长度
算法实现如下,时间复杂度是O( N2 ),空间复杂度是O(N)。
class Solution {
public:
bool PredictTheWinner(vector<int>& nums) {
int dp[21], sum[21];
sum[0] = 0;
for(int i = 0;i < nums.size();i++) {
sum[i+1] = sum[i] + nums[i];
dp[i] = nums[i];
}
for(int i = 1;i < nums.size();i++) {
for(int j = 0;j < nums.size() - i;j++) {
dp[j] = sum[j+i+1] - sum[j] - min(dp[j], dp[j+1]);
}
}
if(2*dp[0] >= sum[nums.size()]) return true;
else return false;
}
};