通用算法 - [博弈论] - 双人取数游戏

410 阅读2分钟

1、问题描述

给定一个数组nums,a、b两人轮流从数组的左端或右端取一个数作为自己的得分,假设两人足够聪明,都采用最优的策略取数,且a先取,问a能能拿到的最大的分数是多少? 示例:

输入:nums=[4,7,5,3] 输出:10 解释:a能拿到的最大分数为7+3=10.

2、解题思路

分析:由题意,我们可以明确以下几点: (1) 当选手aabb在子数组nums[i,...,j]nums[i,...,j]中取数时,无论怎么取,aabb最终的得分之和总是一个固定值,这个固定值等于子数组的所有元素之和; (2) 假设dp[i][j]dp[i][j]表示aa选手在子数组nums[i,...,j]nums[i,...,j]取数时的最优解,由于bb选手每次也是选择最优的解,所以dp[i1][j]dp[i-1][j]或者dp[i][j1]dp[i][j-1]就代表了bb选手在子数组nums[i,...,j]nums[i,...,j]取数时的最优解(因为aa选手取了一个数后剩下的元素要么是nums[i+1,...,j]nums[i+1,...,j]要么是nums[i,...,j1]nums[i,...,j-1])。 (3) 考虑到aabb的得分之和固定,即aa的得分加上bb的得分等于固定值,若想让aa选手的得分最高,则等价于让bb的得分最低。

根据以上结论,我们采用动态规划来解决该问题: (1)定义状态:

dp[i][j]dp[i][j]aa选手在子数组nums[i,...,j]nums[i,...,j]上取数时的最大得分;

(2)状态转移: aa选手在子数组nums[i,...,j]nums[i,...,j]的得分等于子数组之和sum(i,j)sum(i,j)减去bb选手子数组nums[i,...,j]nums[i,...,j]的得分,而aa选手要想得分最高,等价于bb选手的得分最低。

dp[i][j]=sum(i,j)min(dp[i+1][j],dp[i][j1])dp[i][j] = sum(i,j) - min(dp[i+1][j],dp[i][j-1])

(3)确定起始: 当只剩下一个数时,aa取走它。 dp[i][i]=nums[i]dp[i][i]=nums[i]。 (4)确定终止 aa选手在整个数组取数时的最大得分。 dp[0][length1]dp[0][length-1]

注意,由于涉及到区间数组和的问题,因此可以采用前缀和来进行优化。

3、代码实现

int maxScore(vector<int> nums){
	int len = nums.size();
	//求数组的前缀和数组
	vector<int> sums(len,0);
	for(int i = 0; i < len; i++){
		if(i > 0){
			sums[i] = sums[i-1] + nums[i]
		}
		else{
			sums[i] = nums[i];
		}
	}
	
	//动态规划
	vector<vector<int>> dp(len, vector<int>(len,0));
	for(int i = 0; i < len; i++){
		for(int j=i; j >=0; j--){
			if(j == i){
				dp[i][i] = nums[i];
			}
			else{
				int scoresum = j  > 0 ? sum[i] - sum[j-1] : sum[i];
				dp[j][i] = scoresum - min(dp[j+1][i],dp[j][i-1];
			}
		}
	}
	
	return dp[0][len -1];
			
}