动态规划---石子游戏

石子游戏(leetcode877)

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

  1. 状态:游戏的得分与石子的起始和结束位置有关,选择使其位置i和结束位置j作为状态,用dp[i][j]表示以第i个石子为起始,以第j个石子为结束位置时,先手的最高得分。
  2. 状态转移方程:为了便于分析,第一个选手得分减去另一个选手得分,也即记录得分的相对值。先手有两中不同的选择:选择第i个石子,则下一个选手可得的最高分是dp[i+1][j],因此该选手的相对得分为piles[i]-dp[i+1][j];若选择第j个石子,此时同理可得该选手的得分相对值为piles[j]-dp[i][j-1]。由此可得状态转移方程为dp[i][j]=max(piles[i]-dp[i+1][j],piles[j]-dp[i][j-1])。
  3. 初始值:dp[i][i]=piles[i],因为只有一个石子。
  4. 结果:dp[0][N-1],N为石子的长度。

代码

class Solution {
public:
    bool stoneGame(vector<int>& piles) {
        int N = piles.size();
        int dp[N][N];
        memset(dp, 0, sizeof(dp));

        for(int i = 0; i < N; i++){
           dp[i][i] = piles[i];  
        }

        for(int i = N-2; i >=0; i--){
            for(int j = i+1; j < N; j++){
               dp[i][j] = max(piles[i]-dp[i+1][j], piles[j]-dp[i][j-1]);
            }
        }

        return dp[0][N-1] > 0; 
    }
};

石子游戏(leetcode1140)

题目描述
亚历克斯和李继续他们的石子游戏。许多堆石子 排成一行,每堆都有正整数颗石子 piles[i]。游戏以谁手中的石子最多来决出胜负。
亚历克斯和李轮流进行,亚历克斯先开始。最初,M = 1。
在每个玩家的回合中,该玩家可以拿走剩下的 前 X 堆的所有石子,其中 1 <= X <= 2M。然后,令 M = max(M, X)。
游戏一直持续到所有石子都被拿走。
假设亚历克斯和李都发挥出最佳水平,返回亚历克斯可以得到的最大数量的石头。

思路

  1. dp[i][j] 代表当前还剩下i堆石头,M为j的情况下能获取到的最大重量
  2. 状态转移方程为:
    dp[i][j] = max{sums[N] - sums[i] - dp[i - k][max(k, j)]} (1 <= k <= 2 * j)
    sums[N] - sums[i]代表剩下i堆石头的总重量,若本次取k堆,那么下次取的时候M就变成了max(k, j),
    下次能获取到的最大值对应到dp的定义就是dp[i - k][max(k, j)]

代码

class Solution {
public:
    int stoneGameII(vector<int>& piles) {
        int N = piles.size();
        int sums[N+1];
        int dp[N+1][N+1];
        memset(sums, 0, sizeof(sums));
        memset(dp, 0, sizeof(dp));

        for(int i = 0; i < N; ++i){
           sums[i+1] = sums[i] + piles[i];
        }

        for(int i = 0; i <= N; ++i){
            for(int j = i; j <= N; j++){
               dp[i][j] = sums[N] - sums[N-i];   //剩余i堆石头的总和
            }  
        }

        for(int i = 1; i <= N; ++i){
            for(int j = 1; j <= N; ++j){    //剩余i堆里选k堆
                for(int k = 1; k <= 2*j && k <= i; ++k){
                    dp[i][j] = max(dp[i][j], sums[N] - sums[N-i] - dp[i-k][min(max(k,j), N)]); 
                }
            }
        }
        
        return dp[N][1];
    }
};

石子游戏(leetcode1686)

题目描述
Alice 和 Bob 轮流玩一个游戏,Alice 先手。
一堆石子里总共有 n 个石子,轮到某个玩家时,他可以 移出 一个石子并得到这个石子的价值。Alice 和 Bob 对石子价值有 不一样的的评判标准 。双方都知道对方的评判标准。
给你两个长度为 n 的整数数组 aliceValues 和 bobValues 。aliceValues[i] 和 bobValues[i] 分别表示 Alice 和 Bob 认为第 i 个石子的价值。
所有石子都被取完后,得分较高的人为胜者。如果两个玩家得分相同,那么为平局。两位玩家都会采用 最优策略 进行游戏。
请你推断游戏的结果,用如下的方式表示:
如果 Alice 赢,返回 1 。
如果 Bob 赢,返回 -1 。
如果游戏平局,返回 0 。
思路

  1. 贪心算法:贪心算法对每个子问题的解决方案都做出选择,不能回退;动态规划则会根据以前的选择结果对当前进行选择,有回退功能。动态规划主要运用于二维或三维问题,而贪心一般是一维问题。
  2. 假设有又两个石头,Alice和Bob对这两个石头的价值分别是a1、a2、b1、b2。Alice是先手,有两种选择方案,第一种是Alice先选第一块石头,此时Alice获得的价值是a1-b2;第二种是Bob先选第一块石头,此时Alice获胜的价值是a2-b1。故两种方案的价值差为c=(a1-b2)-(a2-b1)=(a1+b1)-(a2+b2)。如果c > 0 那么方案一更优,如果c == 0,那么两种方案价值一样,如果c < 0那么方案二更优。推广得比较每个下标 i 的 a[i] + b[i] 的优劣。
  3. 贪心的策略是:合并两组石头的价值,每次取价值最大的组。Alice先取,故是奇数下标;Bob是偶数下标。

代码

class Solution {
public:
    int stoneGameVI(vector<int>& aliceValues, vector<int>& bobValues) {
        int n = aliceValues.size();
        vector<pair<int,int>> mp;  //总和+下标
        for(int i = 0; i < n; ++i){
           int dis = aliceValues[i] + bobValues[i];
           mp.emplace_back(dis,i);
        }

        sort(mp.rbegin(), mp.rend()); //对价值从大到小排序
        int sum1 = 0, sum2 = 0;
        for(int i = 0; i < n; ++i){
            if(i % 2==0) sum1 += aliceValues[mp[i].second];
            else sum2 += bobValues[mp[i].second]; 
        }

        if(sum1 == sum2) return 0;
        else if(sum1 > sum2) return 1;
        else return -1;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值