leetcode 石子游戏
石子游戏一
亚历克斯和李用几堆石子在做游戏。偶数堆石子排成一行,每堆都有正整数颗石子 piles[i] 。
游戏以谁手中的石子最多来决出胜负。石子的总数是奇数,所以没有平局。
亚历克斯和李轮流进行,亚历克斯先开始。 每回合,玩家从行的开始或结束处取走整堆石头。 这种情况一直持续到没有更多的石子堆为止,此时手中石子最多的玩家获胜。
假设亚历克斯和李都发挥出最佳水平,当亚历克斯赢得比赛时返回 true ,当李赢得比赛时返回 false 。
思路
dp[i][j][0]表示从i到j堆先手所能取得的最大值,dp[i][j][1]表示后手取得的最大值,有转移方程如下:
dp[i][j][0]=max(piles[i]+dp[i+1][j][1],piles[j]+dp[i][j-1][1])
dp[i][j][1]=min(dp[i+1][j][0],dp[i][j-1][0]) 后手状态转移由先手决定
class Solution:
def stoneGame(self, piles: List[int]) -> bool:
n=len(piles)
dp=[[[0,0] for i in range(n+1)] for j in range(n+1)]
for i in range(n):
dp[i][i][0]=piles[i]
dp[i][i][1]=0
for l in range(1,n):
for i in range(0,n-l):
j=i+l
dp[i][j][0]=max(piles[i]+dp[i+1][j][1],piles[j]+dp[i][j-1][1])
dp[i][j][1]=min(dp[i+1][j][0],dp[i][j-1][0])
if dp[0][n-1][0]>dp[0][n-1][1]:
return True
else:
return False
为什么这里dp要有两维状态i和j呢?而石子游戏三中的定义只需要一维,这应该与问题的定义有关。第一个问题中,由于可以取头和尾,右边界是固定的,而问题三中是可以连续取的,右边界固定为n,那么我们只有一维状态即可。
石子游戏二
亚历克斯和李继续他们的石子游戏。许多堆石子 排成一行,每堆都有正整数颗石子 piles[i]。游戏以谁手中的石子最多来决出胜负。
亚历克斯和李轮流进行,亚历克斯先开始。最初,M = 1。
在每个玩家的回合中,该玩家可以拿走剩下的 前 X 堆的所有石子,其中 1 <= X <= 2M。然后,令 M = max(M, X)。
游戏一直持续到所有石子都被拿走。
假设亚历克斯和李都发挥出最佳水平,返回亚历克斯可以得到的最大数量的石头。
思路
现在先手可以取到的范围不是固定的,但还是如问题三一样是连续的一个序列。因此我们可以定义dp[i][j]表示第i给位置,最远达到j位置时先手取得的最大值,sum[i]存储从piles[i]后缀和。那么
可知dp[i][j]=sum[i]-min(dp[i+p][max(p,j/2)],其中1 <=p and p<=(j-i)
class Solution:
def stoneGameII(self, piles: List[int]) -> int:
n=len(piles)
su=0
dp=[[0 for i in range(n+1)] for j in range(n+1)]
for i in range(n-1,-1,-1):
su+=piles[i]
for m in range(1,n+1):
if i+2*m>=n:
dp[i][m]=su
continue
for j in range(1,2*m+1):
if i+j<n:
dp[i][m]=max(dp[i][m],su-dp[i+j][max(j,m)])
return dp[0][1]
石子游戏三
Alice 和 Bob 用几堆石子在做游戏。几堆石子排成一行,每堆石子都对应一个得分,由数组 stoneValue 给出。
Alice 和 Bob 轮流取石子,Alice 总是先开始。在每个玩家的回合中,该玩家可以拿走剩下石子中的的前 1、2 或 3 堆石子 。比赛一直持续到所有石头都被拿走。
每个玩家的最终得分为他所拿到的每堆石子的对应得分之和。每个玩家的初始分数都是 0 。比赛的目标是决出最高分,得分最高的选手将会赢得比赛,比赛也可能会出现平局。
假设 Alice 和 Bob 都采取 最优策略 。如果 Alice 赢了就返回 "Alice" ,Bob 赢了就返回 "Bob",平局(分数相同)返回 "Tie" 。
思路
当前状态下的先手再取完一次后的下一状态变成后手,同理最初的后手下一状态变成先手。我们用dp[i][0]表示第i堆石子时,先手可以取得的最大值,dp[i][1]表示此时后手可以取得的最大值。
那么有转移方程:dp[i][0]=max(dp[i+1][1]+sum(stoneValue[i:i+1]),dp[i+2][1]+sum(stoneValue[i:i+2],dp[i+3][1]+sum(stoneValue[i:i+3])),设dp[i][0]由第i+p个状态转移而来
那么dp[i][1]=dp[i+p][0]
代码如下
class Solution:
def stoneGameIII(self, stoneValue: List[int]) -> str:
n=len(stoneValue)
dp=[[0,0] for i in range(n+3)]
dp[n-1][0],dp[n-1][1]=stoneValue[n-1],0
for i in range(n-2,-1,-1):
mx,mx_p=-float('inf'),-1
for k in range(1,4):
if i+k>n:
break
tmp=(dp[i+k][1] if i+k<n else 0)+sum(stoneValue[i:i+k])
if tmp>mx:
mx=tmp
mx_p=k
dp[i][0]=mx
dp[i][1]=dp[i+mx_p][0] if i+mx_p<n else 0
if dp[0][0]>dp[0][1]:
return "Alice"
elif dp[0][0]<dp[0][1]:
return "Bob"
elif dp[0][0]==dp[0][1]:
return "Tie"
利用问题二的思路,我们可以将二维状态优化为一维。dp[i]表示第i个位置先手取得的最大值,那么dp[i]=sum[i]-min(dp[i+1],dp[i+2],dp[i+3])
for(int n = stoneValue.size(), i = n-1; i >= 0; i--) {
dp[i] = -0x7FFFFFFE;
sum += stoneValue[i];
for(int j = 1; j <= 3; j++) {
dp[i] = max(dp[i], sum - dp[i+j]);
}
}
if(sum - dp[0] == dp[0]) {
return "Tie";
} else if(sum - dp[0] > dp[0]) {
return "Bob";
}
return "Alice";
使用记忆化搜索思路更清晰
石子游戏三
class Solution {
public:
int dp[50010];
int sum[50010],n;
int dfs(int l,vector<int>& piles)
{
if(l==n)
return sum[l]-sum[l-1];
if(l>n)
return 0;
if(dp[l]!=-1)
return dp[l];
int l1=sum[n]-sum[l-1]-dfs(l+1,piles);
int l2=max(l1,sum[n]-sum[l-1]-dfs(l+2,piles));
int l3=max(l2,sum[n]-sum[l-1]-dfs(l+3,piles));
return dp[l]=l3;
}
string stoneGameIII(vector<int>& stoneValue) {
memset(dp,-1,sizeof(dp));
sum[1]=stoneValue[0];
n=stoneValue.size();
for(int i=2;i<=n;i++)
sum[i]=sum[i-1]+stoneValue[i-1];
int l1=dfs(1,stoneValue);
int l2=sum[n]-l1;
if(l1<l2)
return "Bob";
else if(l1==l2)
return "Tie";
else
return "Alice";
}
};
石子游戏2
class Solution {
public:
int dp[110][110];
int sum[110],n;
int dfs(int l,int m,vector<int>& piles)
{
if(l==n)
return sum[l]-sum[l-1];
if(l>n)
return 0;
if(dp[l][m]!=-1)
return dp[l][m];
int tmp=0;
for(int i=1;i<=2*m;i++)
{
if(l+i<=n+1)
tmp=max(sum[n]-sum[l-1]-dfs(l+i,max(m,i),piles),tmp);
}
return dp[l][m]=tmp;
}
int stoneGameII(vector<int>& piles) {
memset(dp,-1,sizeof(dp));
sum[1]=piles[0];
n=piles.size();
for(int i=2;i<=n;i++)
sum[i]=sum[i-1]+piles[i-1];
return dfs(1,1,piles);
}
};
石子游戏一
class Solution {
public:
int dp[510][510];
int sum[510];
//一个非常重要的点:我们不去考虑当前是先手还是后手拿,我们只算在当前这个区间中,按照要求我们最多可以拿多少个
int dfs(int l,int r,int m,vector<int>& piles)
{
if(l>r)
return 0;
if(l==r)
return sum[r]-sum[l-1];
if(dp[l][r]!=-1)
return dp[l][r];
int tmp=-1;
for(int i=1;i<=2*m;i++)
{
int tmp=max(sum[r]-sum[l-1]-dfs(l+i,r,max(m,i),piles),tmp);
}
return dp[l][r]=tmp;
}
bool stoneGame(vector<int>& piles) {
memset(dp,-1,sizeof(dp));
sum[1]=piles[0];
int n=piles.size();
for(int i=2;i<=n;i++)
sum[i]=sum[i-1]+piles[i-1];
int res=dfs(1,n,1,piles);
return res>(sum[n]-res);
}
};
石子游戏五
Alice 和 Bob 轮流玩一个游戏,Alice 先手。
一堆石子里总共有 n 个石子,轮到某个玩家时,他可以 移出 一个石子并得到这个石子的价值。Alice 和 Bob 对石子价值有 不一样的的评判标准 。双方都知道对方的评判标准。
给你两个长度为 n 的整数数组 aliceValues 和 bobValues 。aliceValues[i] 和 bobValues[i] 分别表示 Alice 和 Bob 认为第 i 个石子的价值。
所有石子都被取完后,得分较高的人为胜者。如果两个玩家得分相同,那么为平局。两位玩家都会采用 最优策略 进行游戏。
请你推断游戏的结果,用如下的方式表示:
如果 Alice 赢,返回 1 。
如果 Bob 赢,返回 -1 。
如果游戏平局,返回 0 。
这个题不能用记忆化搜索,因为他没有区间的概念,每次可以拿任意一个位置,我们只能标记此位置是否被拿过。并且有两个数组,我们没办法用一个dfs表示两个状态如Alice的最大得分同时Bob的最大得分。
经过思考可以发现,每个人在拿石子的时候最优操作是自己的价值尽可能大,同时是另一个价值减少的多。因此可以基于a[i]+b[i]排序
class Solution {
public:
int stoneGameVI(vector<int>& aliceValues, vector<int>& bobValues) {
int n=aliceValues.size();
vector<pair<int,int>>a;
for(int i=0;i<n;i++) a.push_back(make_pair(aliceValues[i]+bobValues[i],i ));
sort(a.begin(),a.end());
int alice = 0, bob = 0;
for(int i=n-1;i>=0;i--){
if(i%2) alice+=aliceValues[a[i].second];
else bob+=bobValues[a[i].second];
}
if(alice>bob) return 1;
else if(alice< bob) return -1;
else return 0;
}
};
石子游戏六
石子游戏中,爱丽丝和鲍勃轮流进行自己的回合,爱丽丝先开始 。
有 n 块石子排成一排。每个玩家的回合中,可以从行中 移除 最左边的石头或最右边的石头,并获得与该行中剩余石头值之 和 相等的得分。当没有石头可移除时,得分较高者获胜。
鲍勃发现他总是输掉游戏(可怜的鲍勃,他总是输),所以他决定尽力 减小得分的差值 。爱丽丝的目标是最大限度地 扩大得分的差值 。
给你一个整数数组 stones ,其中 stones[i] 表示 从左边开始 的第 i 个石头的值,如果爱丽丝和鲍勃都 发挥出最佳水平 ,请返回他们 得分的差值 。
题解 dp[l][r]表示先手在[l,r]区间内获得的最大分差,可以表示为当前先手可以获得的得分-(下一轮中下一个人比当前先手获得的最大分差)
class Solution {
public:
int sum[1100];
int dp[1010][1010];
vector<int> stones;
int dfs(int l, int r)
{
if(r-l==1)
return max(stones[r-1],stones[l-1]);
if(dp[l][r]!=-1) return dp[l][r];
int tmp=0;
tmp=max(tmp, sum[r]-sum[l] - dfs(l+1,r)); //A收获的价值 - 下次B比A收获的最大得分差
tmp=max(tmp, sum[r-1]-sum[l-1]- dfs(l,r-1));
return dp[l][r]=tmp;
}
int stoneGameVII(vector<int>& stones) {
int n=stones.size();
this->stones = stones;
memset(dp, -1 ,sizeof(dp));
sum[0] = 0;
for(int i = 0; i < n; i++) sum[i+1] = sum[i] + stones[i] ;
return dfs(1,n);
}
};
石子游戏七
Alice 和 Bob 玩一个游戏,两人轮流操作, Alice 先手 。
总共有 n 个石子排成一行。轮到某个玩家的回合时,如果石子的数目 大于 1 ,他将执行以下操作:
选择一个整数 x > 1 ,并且 移除 最左边的 x 个石子。
将 移除 的石子价值之 和 累加到该玩家的分数中。
将一个 新的石子 放在最左边,且新石子的值为被移除石子值之和。
当只剩下 一个 石子时,游戏结束。
Alice 和 Bob 的 分数之差 为 (Alice 的分数 - Bob 的分数) 。 Alice 的目标是 最大化 分数差,Bob 的目标是 最小化 分数差。
给你一个长度为 n 的整数数组 stones ,其中 stones[i] 是 从左边起 第 i 个石子的价值。请你返回在双方都采用 最优 策略的情况下,Alice 和 Bob 的 分数之差 。
class Solution {
public:
//倒序dp,前缀和
//dp[i][k],第k个人从第i个石子处开始取可以得到的理想分差是多少?对于Alice,理想分差越大越好;Bob则是理想分差越小越好
//dp[i][0] = max (dp[j][1]+sum[j-1]) (j>i) //sum[j-1]是当前从i处取第一个人获得的分数
//dp[i][1] = min ( dp[j][0] - sum[j-1] ) (j>i)
int dp[100010][2];
int sum[100010];
int stoneGameVIII(vector<int>& stones) {
int n=stones.size();
priority_queue<int>q[2];
sum[0]=0;
for(int i=0;i<n;i++) sum[i+1]=sum[i]+stones[i];
dp[n+1][0]=dp[n+1][1]=0;
for(int i=n+1;i>=1;i--){
if(i<=n){
dp[i][0]=q[1].top();
dp[i][1]=(-q[0].top());
}
q[1].push(dp[i][1]+sum[i-1]);
q[0].push(-(dp[i][0]-sum[i-1]));
}
return dp[2][0];
//dp[i]=max(dp[i+1],sum[i]-dp[i+1])
}
};