leetcode1563. 石子游戏 V(Python3、c++)

leetcode1563. 石子游戏 V

几块石子 排成一行 ,每块石子都有一个关联值,关联值为整数,由数组 stoneValue 给出。

游戏中的每一轮:Alice 会将这行石子分成两个 非空行(即,左侧行和右侧行);Bob 负责计算每一行的值,即此行中所有石子的值的总和。Bob 会丢弃值最大的行,Alice 的得分为剩下那行的值(每轮累加)。如果两行的值相等,Bob 让 Alice 决定丢弃哪一行。下一轮从剩下的那一行开始。

剩下一块石子 时,游戏结束。Alice 的分数最初为 0

返回 Alice 能够获得的最大分数

示例 1:

输入:stoneValue = [6,2,3,4,5,5]
输出:18
解释:在第一轮中,Alice 将行划分为 [6,2,3],[4,5,5] 。左行的值是 11 ,右行的值是 14 。Bob 丢弃了右行,Alice 的分数现在是 11 。
在第二轮中,Alice 将行分成 [6],[2,3] 。这一次 Bob 扔掉了左行,Alice 的分数变成了 16(11 + 5)。
最后一轮 Alice 只能将行分成 [2],[3] 。Bob 扔掉右行,Alice 的分数现在是 18(16 + 2)。游戏结束,因为这行只剩下一块石头了。

示例 2:

输入:stoneValue = [7,7,7,7,7,7,7]
输出:28

示例 3:

输入:stoneValue = [4]
输出:0

提示:

  • 1 <= stoneValue.length <= 500
  • 1 <= stoneValue[i] <= 10^6

方法:动态规划(递归+记忆化)

思路:

本题很明显应该使用动态规划的方法。

首先,我们看状态的表示,使用f(i,j)表示对石头数组的子数组stoneValue[i:j+1]可以得到的最大分数。那么我们最后的答案应该为f(0,n-1),即对整个stoneValue数组进行游戏。

下面我们考虑状态的转移。对于f(i,j)进行游戏,我们对这个子数组可以有多种分法(分为左右区间),**我们假设以下标k为左区间的右端点。**那么,就可以分为(i,k)和(k+1,j)两组,这里k的取值为[i,j],那么一共有j-i+1中分法。

我们考虑k的时候,如何进行计算,首先计算出左右两个区间的和,设为left和right,那么根据游戏的规则,有以下几种情况:

  • 如果left>right,那么左边的和大,根据规则,抛弃左边,则分数为right+对右边进行递归,即f(k+1,j)
  • 如果left<right,那么右边的和大,则抛弃右边,分数为left+f(i,k)
  • 如果left和right相等,则需要考虑对两边分别递归,哪个大,因此分数为left + max(f(i,k),f(k+1,j))

在将所有的k遍历之后,最大的分数即为f(i,j)。

对于计算区间的和,我们可以使用presum表示前缀和数组。presum[i]表示前i个数的和(即下标0到i-1的值),那么假设区间为[i,j](闭区间),则这个区间的和为presum[j+1]-presum[i]。

对于动态规划,我们可以递归+记忆化,也可以使用dp数组迭代。对于迭代,时间复杂度为O(N ^ 3),可能会超时,因此使用递归+记忆化来减少一些无意义的计算,详细见代码。

代码:

Python3:
class Solution:
    def stoneGameV(self, stoneValue: List[int]) -> int:
        n = len(stoneValue)
        # 前缀和数组,那么[i,j]的和为presum[j+1]-presum[i]
        presum = [0]*(n+1)
        for i in range(1,n+1):
            presum[i] = stoneValue[i-1] + presum[i-1]
        
        @lru_cache(None)
        def dfs(l,r):
            res = 0
            # 只剩一个石子,得分是0
            if l == r:
                return res
            # 遍历所有可能的分割位置
            for k in range(l,r+1):
                # left和right表示左右区间的和
                left = presum[k+1]-presum[l]
                right = presum[r+1]-presum[k+1]
                # 左边和大于右边和,那么丢弃左边,分数为右边和+对右边递归的结果
                if left > right:
                    res = max(res,right+dfs(k+1,r))
                # 右边和大于左边和,那么丢弃右边,分数为左边和+对左边递归的结果
                elif left < right:
                    res = max(res,left+dfs(l,k))
                # 二者相等,分数为和+左右两边递归结果中较大的
                else:
                    res = max(res,left+max(dfs(l,k),dfs(k+1,r)))
            # 遍历了所有的k,返回最大的res
            return res

        return dfs(0,n-1)
cpp:
class Solution {
public:
    // memo为记忆化备忘录,presum为前缀和,那么[i,j]的和为presum[j+1]-presum[i]
    int memo[501][501];
    int presum[501];
    int stoneGameV(vector<int>& stoneValue) {
        // 填写前缀和数组
        int n = stoneValue.size();
        for (int i = 1; i <= n; i++) presum[i] = stoneValue[i-1] + presum[i-1];
        // 递归计算答案
        return dfs(0,n-1);
    }
    // 递归函数
    int dfs(int l,int r){
        // 如果之前求过这一对值,直接返回
        if (memo[l][r])
            return memo[l][r];
        // res保存答案
        int res = 0;
        // 如果长度为1,那么得分为0
        if (l == r) return res;
        // 使用k分成左右两组,遍历所有可能的分组k
        for (int k = l; k <= r; k++){
            // 表示左右区间的和
            int left = presum[k+1] - presum[l];
            int right = presum[r+1] - presum[k+1];
            // 左边和更大,此时分数为right+对右边递归求值
            if (left > right)
                res = max(res,right+dfs(k+1,r));
            // 右边和更大,此时分数为left+对左边递归求值
            else if(left < right)
                res = max(res,left+dfs(l,k));
            // 左右一样大,此时分数为这个和+对左右分别递归求值的较大值
            else
                res = max(res,left+max(dfs(l,k),dfs(k+1,r)));
        }
        // 遍历所有k的最大res即为答案,更新memo中,返回res
        memo[l][r] = res;
        return res;

    }
};

结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值