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])
    }
};
posted @ 2020-04-21 21:39  blueattack  阅读(511)  评论(0编辑  收藏  举报