石子游戏一
亚历克斯和李用几堆石子在做游戏。偶数堆石子排成一行,每堆都有正整数颗石子 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);
}
};