线性动态规划
字数 2331 2025-12-11 08:42:26

好的,我将随机选择一个线性动态规划领域内,且不在你已提供列表中的题目。我选择的题目是:

带权重的最大子段和问题(Maximum Weighted Subarray Sum with Non-negative Subarray Restriction)

题目描述

我们有一个整数数组 nums,长度为 n。每个元素 nums[i] 都有一个权重值 weight[i](权重值可以是正数、负数或零)。我们的目标是找到一个非空连续子数组 nums[l...r],使得该子数组的权重和(即子数组中每个元素的 weight 值之和)减去子数组的元素值之和(即子数组中每个 nums 值之和)最大化

形式上,我们需要最大化以下表达式的值:

(weight[l] + weight[l+1] + ... + weight[r]) - (nums[l] + nums[l+1] + ... + nums[r])

其中 1 <= l <= r <= n

示例:

输入:
nums =   [3,  -2,   5,  -1]
weight = [2,   1,   4,  -3]

输出:
4
解释:
选择子数组 nums[2...3] 即 [-2, 5]。
权重和 = weight[1] + weight[2] = 1 + 4 = 5  (注意:索引通常从0开始,这里为表述方便用1-based)
元素和 = nums[1] + nums[2] = (-2) + 5 = 3
目标值 = 5 - 3 = 2。

但最佳选择是子数组 nums[0...2] 即 [3, -2, 5]。
权重和 = 2 + 1 + 4 = 7
元素和 = 3 + (-2) + 5 = 6
目标值 = 7 - 6 = 1。这个似乎不是最大?等一下,我们再看。

实际上,最佳选择是子数组 nums[2] 即 [5] 本身。
权重和 = 4
元素和 = 5
目标值 = 4 - 5 = -1。这更小。

让我们重新计算:对于子数组 nums[1...2] (即索引0和1) [3, -2]:
权重和 = 2 + 1 = 3
元素和 = 3 + (-2) = 1
目标值 = 3 - 1 = 2。

但答案输出是4,这意味着存在更好的选择。子数组 nums[2] 即 [5] 并不好。
等等,我给出的示例可能有问题。让我们重新设定一个清晰的示例。

为了避免混淆,让我们定义更清晰的示例。

清晰示例:

输入:
nums   = [1, 2, -3, 4]
weight = [0, 3,  2, 1]

我们需要计算对于每个子数组 (weight_sum - num_sum) 的最大值。
- 子数组 [1]: (0) - (1) = -1
- 子数组 [2]: (3) - (2) = 1
- 子数组 [-3]: (2) - (-3) = 5
- 子数组 [4]: (1) - (4) = -3
- 子数组 [1,2]: (0+3) - (1+2) = 3 - 3 = 0
- 子数组 [2,-3]: (3+2) - (2 + (-3)) = 5 - (-1) = 6
- 子数组 [-3,4]: (2+1) - ((-3)+4) = 3 - 1 = 2
- 子数组 [1,2,-3]: (0+3+2) - (1+2+(-3)) = 5 - 0 = 5
- 子数组 [2,-3,4]: (3+2+1) - (2+(-3)+4) = 6 - 3 = 3
- 子数组 [1,2,-3,4]: (0+3+2+1) - (1+2+(-3)+4) = 6 - 4 = 2

最大值是6,对应子数组 [2, -3](索引1到2,0-based)。

所以,对于这个输入,正确答案应该是 6

解题思路

这是一个经典的线性动态规划(Linear DP) 问题,可以看作是最大子段和(Maximum Subarray Sum) 问题的一个变种。

  1. 问题转化
    定义一个新数组 diff,其中 diff[i] = weight[i] - nums[i]
    那么,对于任意子数组 [l...r],我们要最大化的目标表达式就变成了:
    diff[l] + diff[l+1] + ... + diff[r]
    恰好就是数组 diff 的最大子段和问题

  2. 核心洞察
    原问题被完美转化为:寻找新数组 diff 的最大子段和
    最大子段和问题有一个著名的 Kadane算法,可以在 O(n) 时间内用动态规划解决。

  3. Kadane算法(动态规划)
    dp[i] 表示:以第 i 个元素(diff[i])结尾的所有连续子数组中,最大的子段和是多少

    • 状态转移方程dp[i] = max(diff[i], dp[i-1] + diff[i])
      • 意思是,对于以 i 结尾的子数组,要么我只取 diff[i] 自己作为一个新子数组的开始,要么我把它接到以 i-1 结尾的最佳子数组后面。
    • 初始状态dp[0] = diff[0](数组索引从0开始)。
    • 我们最终要的答案不是 dp[n-1],而是所有 dp[i] 中的最大值,因为最大子段可能以任何位置结尾。
  4. 空间优化
    由于 dp[i] 只依赖于 dp[i-1],我们可以只用两个变量(或一个)来迭代,将空间复杂度从 O(n) 降为 O(1)。

详细解题步骤

我们一步步来,使用上面清晰的例子:nums = [1, 2, -3, 4], weight = [0, 3, 2, 1]

步骤1:构造 diff 数组
计算 diff[i] = weight[i] - nums[i]

  • i=0: diff[0] = 0 - 1 = -1
  • i=1: diff[1] = 3 - 2 = 1
  • i=2: diff[2] = 2 - (-3) = 5
  • i=3: diff[3] = 1 - 4 = -3
    所以,diff = [-1, 1, 5, -3]

步骤2:应用Kadane算法找 diff 的最大子段和

  • 初始化:current_max = diff[0] = -1global_max = current_max = -1
  • 遍历 i 从 1 到 3:
    • i=1 (diff[1]=1):
      current_max = max(diff[1], current_max + diff[1]) = max(1, -1+1) = max(1, 0) = 1
      global_max = max(global_max, current_max) = max(-1, 1) = 1
    • i=2 (diff[2]=5):
      current_max = max(5, 1+5) = max(5, 6) = 6
      global_max = max(1, 6) = 6
    • i=3 (diff[3]=-3):
      current_max = max(-3, 6 + (-3)) = max(-3, 3) = 3
      global_max = max(6, 3) = 6

步骤3:得到答案
diff 数组的最大子段和是 6,这就是原问题的解。

验证:这个最大子段和 6 对应的子数组是 diff[1] + diff[2] = 1 + 5 = 6,即原数组的 nums[1...2] = [2, -3]。与我们之前手动枚举的结果一致。

边界情况与注意事项

  1. 空子数组:题目要求非空连续子数组,所以我们的算法从第一个元素开始初始化是合理的。如果允许空子数组(和为0),则初始化 current_max = 0, global_max = 0,并在遍历中考虑。
  2. 全负数:如果所有 diff[i] 都是负数,Kadane算法仍然有效,它会找到最大的那个负数(即绝对值最小的负数),对应原问题中“损失最小”的那个单元素子数组。
  3. 时间复杂度:O(n),我们只遍历了一次数组。
  4. 空间复杂度:O(1),只用了常数个额外变量。

最终代码框架(Python)

def max_weighted_subarray_sum(nums, weight):
    n = len(nums)
    if n == 0:
        return 0 # 根据题意,可能返回0或负无穷,这里假设返回0

    # 初始化:以第一个元素结尾的最大和
    current_max = weight[0] - nums[0]
    global_max = current_max

    for i in range(1, n):
        diff_i = weight[i] - nums[i]
        # 状态转移:要么从i开始新的一段,要么接上前面的一段
        current_max = max(diff_i, current_max + diff_i)
        # 更新全局最大值
        global_max = max(global_max, current_max)

    return global_max

# 测试示例
nums = [1, 2, -3, 4]
weight = [0, 3, 2, 1]
print(max_weighted_subarray_sum(nums, weight)) # 输出: 6

这个问题的核心在于通过定义 diff 数组,将原问题的复杂目标(权重和减元素和)巧妙地转化为一个标准的最大子段和问题,从而可以用高效的线性动态规划(Kadane算法)解决。

好的,我将随机选择一个 线性动态规划 领域内,且不在你已提供列表中的题目。我选择的题目是: 带权重的最大子段和问题(Maximum Weighted Subarray Sum with Non-negative Subarray Restriction) 题目描述 我们有一个整数数组 nums ,长度为 n 。每个元素 nums[i] 都有一个 权重值 weight[i] (权重值可以是正数、负数或零)。我们的目标是找到一个 非空连续子数组 nums[l...r] ,使得该子数组的 权重和 (即子数组中每个元素的 weight 值之和)减去子数组的 元素值之和 (即子数组中每个 nums 值之和) 最大化 。 形式上,我们需要最大化以下表达式的值: 其中 1 <= l <= r <= n 。 示例: 为了避免混淆,让我们定义更清晰的示例。 清晰示例: 所以,对于这个输入,正确答案应该是 6 。 解题思路 这是一个经典的 线性动态规划(Linear DP) 问题,可以看作是 最大子段和(Maximum Subarray Sum) 问题的一个变种。 问题转化 : 定义一个新数组 diff ,其中 diff[i] = weight[i] - nums[i] 。 那么,对于任意子数组 [l...r] ,我们要最大化的目标表达式就变成了: diff[l] + diff[l+1] + ... + diff[r] 。 这 恰好就是数组 diff 的最大子段和问题 ! 核心洞察 : 原问题被完美转化为: 寻找新数组 diff 的最大子段和 。 最大子段和问题有一个著名的 Kadane算法 ,可以在 O(n) 时间内用动态规划解决。 Kadane算法(动态规划) : 设 dp[i] 表示: 以第 i 个元素( diff[i] )结尾的所有连续子数组中,最大的子段和是多少 。 状态转移方程 : dp[i] = max(diff[i], dp[i-1] + diff[i]) 意思是,对于以 i 结尾的子数组,要么我只取 diff[i] 自己作为一个新子数组的开始,要么我把它接到以 i-1 结尾的最佳子数组后面。 初始状态 : dp[0] = diff[0] (数组索引从0开始)。 我们最终要的答案不是 dp[n-1] ,而是所有 dp[i] 中的最大值,因为最大子段可能以任何位置结尾。 空间优化 : 由于 dp[i] 只依赖于 dp[i-1] ,我们可以只用两个变量(或一个)来迭代,将空间复杂度从 O(n) 降为 O(1)。 详细解题步骤 我们一步步来,使用上面清晰的例子: nums = [1, 2, -3, 4] , weight = [0, 3, 2, 1] 。 步骤1:构造 diff 数组 计算 diff[i] = weight[i] - nums[i] 。 i=0: diff[0] = 0 - 1 = -1 i=1: diff[1] = 3 - 2 = 1 i=2: diff[2] = 2 - (-3) = 5 i=3: diff[3] = 1 - 4 = -3 所以, diff = [-1, 1, 5, -3] 。 步骤2:应用Kadane算法找 diff 的最大子段和 初始化: current_max = diff[0] = -1 , global_max = current_max = -1 。 遍历 i 从 1 到 3: i=1 (diff[ 1 ]=1): current_max = max(diff[1], current_max + diff[1]) = max(1, -1+1) = max(1, 0) = 1 global_max = max(global_max, current_max) = max(-1, 1) = 1 i=2 (diff[ 2 ]=5): current_max = max(5, 1+5) = max(5, 6) = 6 global_max = max(1, 6) = 6 i=3 (diff[ 3 ]=-3): current_max = max(-3, 6 + (-3)) = max(-3, 3) = 3 global_max = max(6, 3) = 6 步骤3:得到答案 diff 数组的最大子段和是 6 ,这就是原问题的解。 验证 :这个最大子段和 6 对应的子数组是 diff[1] + diff[2] = 1 + 5 = 6 ,即原数组的 nums[1...2] = [2, -3] 。与我们之前手动枚举的结果一致。 边界情况与注意事项 空子数组 :题目要求 非空 连续子数组,所以我们的算法从第一个元素开始初始化是合理的。如果允许空子数组(和为0),则初始化 current_max = 0 , global_max = 0 ,并在遍历中考虑。 全负数 :如果所有 diff[i] 都是负数,Kadane算法仍然有效,它会找到最大的那个负数(即绝对值最小的负数),对应原问题中“损失最小”的那个单元素子数组。 时间复杂度 :O(n),我们只遍历了一次数组。 空间复杂度 :O(1),只用了常数个额外变量。 最终代码框架(Python) 这个问题的核心在于 通过定义 diff 数组,将原问题的复杂目标(权重和减元素和)巧妙地转化为一个标准的最大子段和问题 ,从而可以用高效的线性动态规划(Kadane算法)解决。