好的,我将随机选择一个线性动态规划领域内,且不在你已提供列表中的题目。我选择的题目是:
带权重的最大子段和问题(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) 问题的一个变种。
-
问题转化:
定义一个新数组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
- i=1 (diff[1]=1):
步骤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)
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算法)解决。