LeetCode 877. 石子游戏(区间dp)

117 篇文章 0 订阅
48 篇文章 1 订阅

【题目描述】:
亚历克斯和李用几堆石子在做游戏。偶数堆石子排成一行,每堆都有正整数颗石子 piles[i] 。

游戏以谁手中的石子最多来决出胜负。石子的总数是奇数,所以没有平局。

亚历克斯和李轮流进行,亚历克斯先开始。 每回合,玩家从行的开始或结束处取走整堆石头。 这种情况一直持续到没有更多的石子堆为止,此时手中石子最多的玩家获胜。

假设亚历克斯和李都发挥出最佳水平,当亚历克斯赢得比赛时返回 true ,当李赢得比赛时返回 false 。

示例:

输入:[5,3,4,5]
输出:true
解释:
亚历克斯先开始,只能拿前 5 颗或后 5 颗石子 。
假设他取了前 5 颗,这一行就变成了 [3,4,5] 。
如果李拿走前 3 颗,那么剩下的是 [4,5],亚历克斯拿走后 5 颗赢得 10 分。
如果李拿走后 5 颗,那么剩下的是 [3,4],亚历克斯拿走后 4 颗赢得 9 分。
这表明,取前 5 颗石子对亚历克斯来说是一个胜利的举动,所以我们返回 true 。

提示:

2 <= piles.length <= 500
piles.length 是偶数。
1 <= piles[i] <= 500
sum(piles) 是奇数。

【思路】
看起来是个博弈论,其实经过分析,这就是一道区间dp的题。
最近一直在研究区间dp,昨天做了个括号匹配,今天早上刚做一个合并石子(待会儿上传),然后想在leetcode里面坐下dp题,没想到这一题就是一道区间dp,正好用上了!

区间dp核心思想,原问题是一个大区间的问题,它是建立在小区间问题的基础之上,如果小区间问题全部解决,那么大区间的问题就解决了~
区间dp往往有一个显著特征,他们的遍历顺序都是按对角线方向来遍历的!仔细回想一下括号匹配和合并石子的遍历顺序,有没有发现?

那么为什么说这一题是一个区间dp呢?是这样的,我设f(i, j)表示区间i–j上,先拿石子的人能拿到石子的最大值。 那么,我们最后所求的结果就是 f(0, length - 1) 对吧。(length是指石子数组的长度)

那我们想一想,什么情况下,先手的人必赢?对,就是f(0, length - 1) > sum / 2呀! 如果我先手在这一堆石子中能拿到的最大值大于石子总数的一半,那么后手的人无论怎么拿都会失败!所以这种情况下我们返回true,否则我们返回false(不存在平局)。

因此,在这个基础上,我脑海里瞬间就蹦出了状态转移方程!
f(i, ,j) = max(a[i] + sum[j] - sum[i] - f(i + 1, j), a[j] + sum[j - 1] - sum[i - 1] - f(i , j - 1))
因为你每次只能在开头或结尾拿石子,所以涉及到两个递归子问题 f(i + 1, j)和f(i , j - 1)。因为你是先手,所以 f(i + 1, j)和f(i , j - 1)表示的是后手的人所能拿的最大值,那么此时你能拿的石子就应该是剩余的石子数减去后手的人拿的石子数 即 sum[j] - sum[i] - f(i + 1, j),sum[i]表示前i个石子的数量之和

这个题总体来说与合并石子那个经典问题很像,正是早上刚刚做完那个题,所以启发了我这一题的思路!
AC代码:

class Solution {
public:
    int length;
    int dp[505][505];       //设dp[i][j]表示区间i--j上先取的人最多能取到的石子最大数
    int sum[505];          //sum[i]表示前i个的石子之和
    
    int DP(vector<int>& piles)
    {
        //先把主对角线填充好
        for(int i = 0;i < length;i++)
        {
            dp[i][i] = piles[i];
        }
        //else, 区间dp
        for(int len = 1;len < length;len++)
        {
            for(int i = 0;i < length;i++)
            {
                int j = len + i;
                if(j >= length) continue;           //防止出界
                dp[i][j] = max(piles[i] + sum[j] - sum[i] - dp[i + 1][j], piles[j] + sum[j - 1] - sum[i - 1] - dp[i][j - 1]);
            }
        }
        return dp[0][length - 1];
    }
    
    bool stoneGame(vector<int>& piles) {
        length = piles.size();
        //填充sum数组
        sum[0] = piles[0];
        for(int i = 1;i < length;i++)
        {
            sum[i] = sum[i - 1] + piles[i];
        }
        int res = DP(piles);
        if(res > sum[length - 1] / 2)
            return true;
        else
            return false;
    }
};
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值