• 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);
        }
    };
    
  • 相关阅读:
    spring boot 2.0.3+spring cloud (Finchley)2、搭建负载均衡Ribbon (Eureka+Ribbon+RestTemplate)
    spring boot 2.0.3+spring cloud (Finchley)1、搭建服务注册和发现组件Eureka 以及构建高可用Eureka Server集群
    windows下安装redis
    Spring注解@Resource和@Autowired的区别
    spring常用管理bean注解
    maven中package和install命令的区别
    SpringBoot打war包并部署到tomcat下运行
    使用jquery.qrcode生成二维码及常见问题解决方案
    动态生成二维码插件 jquery.qrcode.js
    Notepad++的列编辑功能
  • 原文地址:https://www.cnblogs.com/flightless/p/12748062.html
Copyright © 2020-2023  润新知