leetcode 1872. 石子游戏VIII

本文探讨了Alice和Bob在石子游戏中如何通过动态规划实现最优策略,以最大化分数差。关键在于理解每个玩家的策略目标并利用dp数组高效计算最大收益。
1872. 石子游戏 VIII

Alice 和 Bob 玩一个游戏,两人轮流操作, Alice 先手 。总共有 n n n 个石子排成一行。轮到某个玩家的回合时,如果石子的数目大于 1 ,他将执行以下操作:
选择一个整数 x > 1 x > 1 x>1 ,并且 移除 最左边的 x x x 个石子。将移除的石子价值之和累加到该玩家的分数中。将一个新的石子放在最左边,且新石子的值为被移除石子值之和。当只剩下一个石子时,游戏结束。Alice 和 Bob 的 分数之差 为 (Alice 的分数 - Bob 的分数) 。 Alice 的目标是最大化分数差,Bob 的目标是最小化分数差

给你一个长度为 n n n 的整数数组 s t o n e s stones stones ,其中 s t o n e s [ i ] stones[i] stones[i] 是 从左边起 第 i i i 个石子的价值。请你返回.在双方都采用最优策略的情况下,Alice 和 Bob 的 分数之差 。

示例 1:

输入:stones = [-1,2,-3,4,-5]
输出:5
解释:
- Alice 移除最左边的 4 个石子,得分增加 (-1) + 2 + (-3) + 4 = 2 ,并且将一个价值为 2 的石子放在最左边。stones = [2,-5] 。
- Bob 移除最左边的 2 个石子,得分增加 2 + (-5) = -3 ,并且将一个价值为 -3 的石子放在最左边。stones = [-3] 。
  两者分数之差为 2 - (-3) = 5 。

提示:

  • n == stones.length
  • 2 <= n <= 10^5
  • -10^4 <= stones[i] <= 10^4

思路:问题目标是Alice 的目标是最大化分数差,Bob 的目标是最小化分数差。这里的最大最小有点头疼,换一种说法,Alice 的目标是最大化(Alice 的分数 - Bob 的分数),Bob的目标是最大化(Bob的分数 - Alice 的分数),各自维护自己与对手的最大分数差。

每一次玩家至少拿两块石头,假设原先石子堆为stones = [-1,2,-3,4,-5],编号1-5,当用户某一次取到了编号最大的石子i,那么他这一次拿到的分数是 ∑ k = 1 i s t o n e k \sum_{k=1}^{i}stone_k k=1istonek

在这里插入图片描述

如图所示,某一次Bob取到了编号最大为3的石子,得到的分数为 1 + ( − 3 ) = ( − 1 ) + 2 + ( − 3 ) = − 2 1+(-3) = (-1) + 2 +(-3) = -2 1+(3)=(1)+2+(3)=2

本题需要使用动态规划求解,但不必记录每一个过程中Alice,Bob的分数差记录。记dp[i]表示,玩家(不用区分是Alice,还是Bob,因为两个人的目标都是是自己和对手的分数差最大)取编号[i,n]的中某一块石子后,能够产生的最大分数差。状态转移方程:
d p [ i ] = m a x { s t o n e s [ 1 ] + . . . + s t o n e s [ j ] − d p [ j + 1 ] } , j ≥ i dp[i]=max\{stones[1]+...+stones[j]-dp[j+1]\},j \geq i dp[i]=max{stones[1]+...+stones[j]dp[j+1]},ji

class Solution {
public:
    int stoneGameVIII(vector<int>& stones) {
        int len = stones.size();
        vector<int> presum(len+1,0);
        for(int i=1;i<=len;i++)
            presum[i] = presum[i-1] + stones[i-1];

        vector<int> dp(len+1, INT_MIN);
        for(int i=len;i>=0;i--){
            dp[i] = presum[len];
            for(int j=i;j<len;j++){
                dp[i] = max(dp[i],presum[j]-dp[j+1]);
            }
        }
        return dp[2];
    }
};

算法复杂度 O ( N 2 ) O(N^2) O(N2),程序会超时。

代码存在冗余,

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

改进思路:

  1. presum[j]-dp[j+1]在过程中需要要维护一个最大值即可,不需要重复计算。

  2. dp[i+1]就记录了取[i+1,n]的石子带来的最大分数差,所以只需要再计算presum[i]-dp[j+1]

两者思路其实是一样的,取第二点优化,状态转移方程可以变为:
d p [ i ] = m a x { d p [ i + 1 ] , s t o n e s [ 1 ] + . . . + s t o n e s [ i ] − d p [ i + 1 ] } dp[i]=max\{dp[i+1],stones[1]+...+stones[i]-dp[i+1]\} dp[i]=max{dp[i+1],stones[1]+...+stones[i]dp[i+1]}

class Solution {
public:
    int stoneGameVIII(vector<int>& stones) {
        int len = stones.size();
        vector<int> presum(len+1,0);
        for(int i=1;i<=len;i++)
            presum[i] = presum[i-1] + stones[i-1];

        vector<int> dp(len+1);
        dp[len] = presum[len];
        for(int i=len-1;i>=0;i--){
            dp[i] = max(dp[i+1],presum[i]-dp[i+1]);
        }

        return dp[2];
    }
};

注意:程序最后返回的dp[2]原因是,Alice作为先手第一次必须要至少取到编号2的石子。

问题主要的思路点就是,不再区分谁取了石子,而是用dp[i]表示取了编号[i,n]的中某一块石子后,能够产生的最大分数差。

相似题目

877. 石子游戏

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值