leetcode
leetcode 1401 ~ 1450
石子游戏 V

石子游戏 V

难度:

标签:

题目描述

代码结果

运行时间: 764 ms, 内存: 55.6 MB


/*
题目思路:
我们可以使用Java Stream和动态规划来解决这个问题。与传统方法类似,我们可以使用前缀和数组来快速计算区间的和,并使用流操作来遍历和计算动态规划的状态。
*/

import java.util.stream.IntStream;

public class StoneGameStream {
    public int stoneGameV(int[] stoneValue) {
        int n = stoneValue.length;
        int[] prefixSum = new int[n + 1];
        IntStream.range(0, n).forEach(i -> prefixSum[i + 1] = prefixSum[i] + stoneValue[i]);
        int[][] dp = new int[n][n];
        IntStream.range(1, n).forEach(len -> {
            IntStream.range(0, n - len).forEach(i -> {
                int j = i + len;
                IntStream.range(i, j).forEach(k -> {
                    int leftSum = prefixSum[k + 1] - prefixSum[i];
                    int rightSum = prefixSum[j + 1] - prefixSum[k + 1];
                    if (leftSum < rightSum) {
                        dp[i][j] = Math.max(dp[i][j], leftSum + dp[i][k]);
                    } else if (leftSum > rightSum) {
                        dp[i][j] = Math.max(dp[i][j], rightSum + dp[k + 1][j]);
                    } else {
                        dp[i][j] = Math.max(dp[i][j], leftSum + Math.max(dp[i][k], dp[k + 1][j]));
                    }
                });
            });
        });
        return dp[0][n - 1];
    }
}

解释

方法:

该题解采用了动态规划的思路,定义状态dp[i,j]表示在子数组stoneValue[i:j]中Alice可以获得的最大分数。首先使用前缀和数组A来优化区间和的计算,使得任意子数组的和可以在O(1)时间内得到。对于任何一个分割位置k,计算左右子数组的和,并基于这两个和更新Alice的最大可能分数。转移方程考虑了三种情况:左子数组和小于、等于、或大于右子数组和。使用记忆化递归(通过cache装饰器)避免重复计算相同状态,从而提高效率。在每个状态下,先计算所有可能的分数,并按分数排序,然后只考虑可能提高最大分的选项来进一步优化运算。这种方式称为剪枝,减少了不必要的递归调用。

时间复杂度:

O(n^3)

空间复杂度:

O(n^2)

代码细节讲解

🦆
为什么在动态规划的状态转移方程中,需要对左右子数组和进行比较,而不是直接计算可能的最大分数?
在'石子游戏V'题目中,Alice的目标是最大化自己的分数,这取决于每次分割后子数组的石子总和。因为Alice的得分基于较小的子数组总和,所以比较左右子数组的和是必要的,以确定哪个子数组的和较小(因为Alice会选择这个子数组的石子总和作为自己的分数)。如果直接计算可能的最大分数而不进行比较,就无法正确模拟游戏规则中的这一策略,即总是基于较小的子数组和来增加分数。
🦆
在递归函数`game`中,`reward_list`是如何确保优化计算的,具体是基于什么逻辑进行排序和剪枝的?
在函数`game`中,`reward_list`包含每个可能的分割点及其对应的最小子数组和,这个最小值是Alice能从当前分割获得的分数。`reward_list`按这个可能得到的分数从高到低进行排序,这样做的目的是优先考虑那些可能给Alice带来更高分数的分割点。剪枝操作则是基于一个观察:一旦找到一个分割点使得分数未能超过当前已知的最大分数,则不再考虑更低分数的分割点,因为它们不可能提供更高的分数。这种方法减少了不必要的计算,提高了效率。
🦆
函数`f`在处理左右子数组和相等的情况时,为什么会考虑两边的最大值,这种策略在哪些情况下是必需的?
在左右子数组和相等的情况下,Alice有两个选择:左边或右边的子数组。因为这两个子数组的和相等,Alice的得分将是这个共同的和值加上两边继续游戏可能得到的最大分数。因此,为了最大化Alice的总分数,需要考虑两边继续游戏的最大值。这种策略在分割点左右子数组和相等时是必需的,因为忽略任一侧可能的最大值都可能导致Alice失去获得更高总分的机会。
🦆
在实现中,使用了前缀和数组A来优化计算,这种方法有哪些潜在的缺点或在特定情况下可能不那么有效?
使用前缀和数组A可以大大优化连续子数组和的计算,将其从O(n)降低到O(1),这对于重复计算区间和的问题很有帮助。然而,这种方法的缺点包括额外的空间复杂度(需要存储整个前缀和数组),以及前缀和数组的初始构建时间(虽然是线性时间,但在非常大的输入上仍然显著)。此外,在更新或修改原始数组的情况下,前缀和数组需要被重新计算,这在动态或实时数据变更的场景中可能不是最有效的方案。

相关问题