leetcode——石子游戏系列题目

石子游戏

亚历克斯和李用几堆石子在做游戏。偶数堆石子排成一行,每堆都有正整数颗石子 piles[i] 。
游戏以谁手中的石子最多来决出胜负。石子的总数是奇数,所以没有平局。
亚历克斯和李轮流进行,亚历克斯先开始。 每回合,玩家从行的开始或结束处取走整堆石头。 这种情况一直持续到没有更多的石子堆为止,此时手中石子最多的玩家获胜。
假设亚历克斯和李都发挥出最佳水平,当亚历克斯赢得比赛时返回 true ,当李赢得比赛时返回 false 。

定义二维数组 dp[i][j],其行数和列数都等于石子的堆数,dp[i][j]表示当剩下的石子堆为下标 i 到下标 j 时,当前玩家与另一个玩家的石子数量之差的最大值。

则有转移方程:

注意是轮流取,所以是减。

dp[i][j]=max(piles[i] - dp[i + 1][j], piles[j] - dp[i][j - 1])

class Solution {
public:
	bool stoneGame(vector<int>& piles) 
	{
		int s = piles.size();
		vector<  vector < int > > dp(s, vector<int >(s, 0));
		for (int i = 0; i < s; i++)
		{
			dp[i][i] = piles[i];
		}
		for (int i = 0; i<s; i++)
		{//只有i<j才有意义
			for (int j = i+1; j < s; j++)
			{
				dp[i][j] = max(piles[i] - dp[i + 1][j], piles[j] - dp[i][j - 1]);
			}
		}
		return dp[0][s - 1];
	}
};

石子游戏 II

在这里插入图片描述

动态规划

用动态规划 ,首先我们要存储状态,那么肯定需要存储的状态即为当前的下标,和M的值。

如果只有一堆的话,自然是很好计算的,全部取完即可。但是剩很多堆时不好算(依赖后面的状态)。所以我们选择从后往前递推。

那么定义dp[i][j]表示,剩余i堆时,M=j时,先取的人能获得的最多石子数。

自然有 i+2*M>=len,可以取完剩下的,那就取完。

剩下的堆数不能全部取走,那么最优情况就是让下一个人取的更少。

dp[i][M] = max(dp[i][M], presum - dp[i + x][max(x, M)])

 class Solution {
 public:
	 int stoneGameII(vector<int>& piles) {
		 int n = piles.size();
		 vector<vector<int>>dp(n, vector<int>(n + 1, 0));
		 int presum;
		 for (int i = n - 1; i >= 0; i--)
		 {
			 presum += piles[i];
			 for (int M = 1; M <= n; M++)
			 {
				 if (i + 2 * M >= n)
					 dp[i][M] = presum;
				 else 
				 {
					 for (int x = 1; x <= 2 * M; x++)
					 {
						 dp[i][M] = max(dp[i][M], presum - dp[i + x][max(x, M)]);
					 }
				 }
			 }
		 }
		 return dp[0][1];
	 }
 };
递归
 class Solution {
 private:
	 unordered_map<pair< int, int>, int > m;
	 vector<int> s;//后缀和
	 int n;
	 int dfs(int i, int M)
	 {//从i开始取
		 if (m.find({ i,M }) != m.end())
			 return m[{i, M}];
		 if (i >= n)
			 return 0;
		 if (i + M * 2 >= n)
			 return s[i];
		 int best = 0;
		 for (int x = 1; x <=2 * M; x++)
		 {
			 //减去对方最优策略
			 best = max(best, s[i] - dfs(i+x, max(x, M)));
		 }
		 m[{i, M}] = best;
		 return best;
	 }
 public:
	 int stoneGameII(vector<int>& piles) {
		 this->n = piles.size();
		 s = vector<int>(n+1, 0);
		 for (int i = n - 1; i >= 0; i--)
			 s[i] = s[i + 1] + piles[i];
		 return dfs(0, 1);
	 }
 };

石子游戏III

在这里插入图片描述

定义dp[i]为剩余i个时,先手获得的最大分数。那么留给下一位的最大分数只能为dp[i+1],dp[i+2],dp[i+3]中的一个。

presum为i到数组末尾的和。

dp[i]=max(presum[i]-dp[j]), i+1<=j<=i+3

所以 dp[i]=presum[i]-min(dp[j])

 class Solution {
 public:
	 string stoneGameIII(vector<int>& stoneValue) {
		 int n = stoneValue.size();
		 vector<int> presum(n, 0);
		 presum[n - 1] = stoneValue[n - 1];
		 for (int i = n - 2; i >= 0; i--)
		 {
			 presum[i] = presum[i + 1] + stoneValue[i];
		 }
		 vector<int> dp(n+1, 0);
		 dp[n] = 0;
		 for (int i = n - 1; i >= 0; i--)
		 {
			 int best = dp[i+1];
			 for (int j = i + 2; j <= i + 3 && j <=n; j++)
				 best = min(best, dp[j]);
			 dp[i] = presum[i] - best;
		 }
		 if (dp[0] * 2 == presum[0])
			 return "Tie";
		 return dp[0]*2>presum[0]? "Alice" : "Bob";
	 }
 };

石子游戏IV

在这里插入图片描述

假设dp[i]时先手处于必胜态,则一定有dp[i-k*k]为必败态。也就是说,当先手在面对 i 颗石子时,可以选择取走 k * k颗,剩余的 i-k * k 颗对于后手来说是必败态,因此先手会获胜。

边界条件为 f[0]=false,即没有石子时,先手会输掉游戏。

动态规划
 class Solution {
 public:
	 bool winnerSquareGame(int n) {
		 vector<int>dp(n + 1, 0);
		 for (int i = 0; i <= n; i++)
		 {//从剩余0个开始递推
			 for (int k = 1; k * k <= i; k++)
			 {
				 if (!dp[i-k*k])
				 {
					 dp[i] = 1;
				 }
			 }
		 }
		 return dp[n];
	 }
 };
DFS
class Solution {
 private:
	 unordered_map<int, int> cache;
	 bool dfs(int n)//剩余n个时
	 {
		 if (n == 1) return true;
		 if (cache.find(n) != cache.end())return cache[n];
		 for (int i = 1; i * i <= n; i++)
		 {
			 if (!dfs(n - i * i))//另一个人剩余n-i*i必输时,n就赢
			 {
				 cache[n] = 1;
				 return true;
			 }
		 }
		 cache[n] = 0;
		 return false;
	 }
 public:
	 bool winnerSquareGame(int n) {
		 return dfs(n);
	 }
 };

石子游戏V

在这里插入图片描述

用dp[i][j]表示当 Alice 面对数组 stoneValue中从位置 i 到 j 这一段连续的石子时,她能获得的最大分数。由于 Alice 需要选择将这一段石子分成两部分,因此我们可以枚举分隔位置 。

对于左右两边的和,记为suml,sumr

suml>sumj,那么肯定丢弃左侧部分,对应的,小于则丢弃右边部分。

如果两行的值相等,Bob 让 Alice 决定丢弃哪一行。那就两边继续搜索比谁大。

 class Solution {
 private:
	 vector<vector<int>> cache;
	 int dfs(vector<int>& sum, int l, int r)
	 {
		 if(l == r)return 0;
		 if (cache[l][r]!=-1)return cache[l][r];
		 cache[l][r] = 0;
		 for (int i = l + 1; i <= r; i++)
		 {
			 int lsum = sum[i] - sum[l];
			 int rsum = sum[r+1] - sum[i];
			 if (lsum < rsum)
				 cache[l][r] = max(cache[l][r], lsum + dfs(sum, l, i - 1));
			 else if (lsum > rsum)
				 cache[l][r] = max(cache[l][r], rsum + dfs(sum, i, r));
			 else
				 cache[l][r] = max(cache[l][r],lsum+max(dfs(sum, l, i - 1), dfs(sum, i, r)));
		 }
		 return cache[l][r];
	 }
 public:
	 int stoneGameV(vector<int>& stoneValue) {
		 int size = stoneValue.size();
		 cache = vector<vector<int>>(size + 1, vector<int>(size + 1, -1));
		 vector<int> sum(size + 1, 0);//[n,m)
		 for (int i = 0; i < size; i++)
			 sum[i + 1] = sum[i] + stoneValue[i];
		 return dfs(sum, 0, size - 1);
	 }
 };

石子游戏 VI

在这里插入图片描述

我们可以这么想:石子分数高我们需要拿,有石子对于bob来说分数也很高,我们也需要拿(因为尽可能要让bob的分数最少)。

存在两个方案 :

alice的 a1,b1

bob的 a2,b2

我们比较a1-b2(alice拿了a1,bob只能拿b2)与b1-a2的差值,即比较a1+a2和b1+b2。

由此 将两数组合并,排序,偶数位 alice的 ,获得排序后对应下标的石头价值即可。

 class Solution {
 public:
	 int stoneGameVI(vector<int>& aliceValues, vector<int>& bobValues) {
		 int size = aliceValues.size();
		 int sum_a = 0, sum_b = 0;
		 vector<pair<int, int>> s(size, {0,0});
		 for (int i = 0; i < size; i++)
		 {
			 s[i] = { aliceValues[i] + bobValues[i],i };
		 }
		 sort(s.begin(), s.end(), [](const pair<int, int> a, const pair<int, int> b)
			 {
				 return a.first > b.first;
			 });
		 for (int i = 0; i < size; i++)
		 {
			 if (i % 2)
				 sum_b += bobValues[s[i].second];
			 else
				 sum_a += aliceValues[s[i].second];

		 }
		 if (sum_a == sum_b)
			 return 0;
		 return sum_a > sum_b? 1:-1;
	 }
 };

石子游戏 VII

在这里插入图片描述

所以 最大得分差 可以理解为此次操作之后,A 所收获的价值 - 下次B 比A的得分差的最大值。
如果是 B 操作,那么就是 B 所收获的价值 - 下次A比B得分差的最大值
所以这次 DP 也用类似的状态。维护一个sum前缀和数组。

dp[i][j]为i-j的当前玩家与另一个玩家得分差的最大值。

当j-i==1,肯定删掉一个较小的石头,取最大得分。

剩下的情况 要么左边删,要么右边删。

class Solution {
 public:
	 int stoneGameVII(vector<int>& stones) {
		 int size = stones.size();
		 vector<int> sum(size + 1, 0);
		 for (int i = 0; i < size; i++)
		 {
			 sum[i + 1] = sum[i] + stones[i];
			 //[0,i);
		 }
		 vector<vector<int>>dp(size, vector<int>(size, 0));
		 for (int i = size - 1; i >= 0; i--)
		 {
			 for (int j = i + 1; j < size; j++)
			 {
				 if (j - i == 1)
					 dp[i][j] = max(stones[i], stones[j]);
				 else//[0,j+1)-[0,i+1)=[i+1,j]
					 dp[i][j] = max(sum[j + 1] - sum[i + 1] - dp[i + 1][j],
						 //[0,j)-[0,i)    =[i,j-1]
						 sum[j] - sum[i] - dp[i][j - 1]);
			 }
		 }
		 return dp[0][size - 1];
	 }
 };
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值