问题复现:
小B拥有一个数组 a,她使用这个数组构造了一个新数组 b。其中,a[i] 表示在新数组 b 中有 a[i] 个 i+1。例如,若 a = [2, 3, 1],那么新数组 b = [1, 1, 2, 2, 2, 3],因为 a[0] = 2 代表数组 b 中有 2 个 1,a[1] = 3 代表数组 b 中有 3 个 2,a[2] = 1 代表数组 b 中有 1 个 3。
现在,你需要帮助小B求出 b 数组中所有连续子数组的极差之和。
由于答案可能非常大,请对 109+7109+7 取模。数组的极差定义为子数组的最大值减去最小值。
解题思路
1. 构造新数组 b
数组 b 的构造过程很直观:从数组 a 开始,逐个遍历每个元素 a[i],将 i+1 重复 a[i] 次添加到新数组 b 中。例如:
- 输入
a = [2, 3, 1] - 输出
b = [1, 1, 2, 2, 2, 3]
这一步通过简单的遍历即可实现,时间复杂度为 O(n),其中 n 是数组 a 的长度。
2. 极差之和的分解
对于数组 b,要计算所有连续子数组的极差之和。直观的暴力解法是:
- 枚举所有子数组。
- 计算每个子数组的最大值和最小值。
- 求得该子数组的极差并累加。
这种方法的时间复杂度是 O(n²) 或更高(如果嵌套求最大值与最小值)。在输入规模较大时会超时,因此需要更优化的思路。
3. 极差的优化计算:单调栈 + 数学分析
(1) 极差的分解
每个子数组的极差可以分解为两部分贡献:
- 元素
b[i]作为子数组 最大值 时的贡献。 - 元素
b[i]作为子数组 最小值 时的贡献。
(2) 数学归纳
对于数组 b 中的某个元素 b[i],我们计算它对所有子数组的贡献:
-
当
b[i]作为 最大值:-
它的贡献范围是:
- 左边最近比它小的元素的下标记为
left_max[i]。 - 右边最近比它小的元素的下标记为
right_max[i]。
- 左边最近比它小的元素的下标记为
-
贡献的子数组数量 =
(i - left_max[i]) * (right_max[i] - i) -
总贡献 =
b[i] * 子数组数量
-
-
当
b[i]作为 最小值:-
它的贡献范围是:
- 左边最近比它大的元素的下标记为
left_min[i]。 - 右边最近比它大的元素的下标记为
right_min[i]。
- 左边最近比它大的元素的下标记为
-
贡献的子数组数量 =
(i - left_min[i]) * (right_min[i] - i) -
总贡献 =
b[i] * 子数组数量
-
最终,每个元素的净贡献为: b[i]⋅(最大值贡献子数组数量−最小值贡献子数组数量)
4. 使用单调栈计算贡献范围
单调栈是一种高效计算范围的方法,可以在线性时间内完成。我们分别用两次单调栈来处理最大值和最小值的范围:
- 单调递减栈: 计算元素作为最大值的贡献范围。
- 单调递增栈: 计算元素作为最小值的贡献范围。
单调栈的关键是利用栈的特性维护顺序关系,使得在遇到新元素时能够快速找到其左右边界。
单调栈的构造过程时间复杂度为 O(n)。
def solution(n, a):
# 构造新数组 b
b = []
for i in range(n):
b.extend([i + 1] * a[i]) # 将 i+1 重复 a[i] 次加入 b
mod = 10**9 + 7
length = len(b)
# 单调栈计算左右边界
def calculate_contributions(b, is_max):
stack = []
left = [-1] * length
right = [length] * length
for i in range(length):
while stack and ((b[stack[-1]] < b[i]) if is_max else (b[stack[-1]] > b[i])):
right[stack.pop()] = i
left[i] = stack[-1] if stack else -1
stack.append(i)
return left, right
# 计算最大值和最小值的左右边界
left_max, right_max = calculate_contributions(b, is_max=True)
left_min, right_min = calculate_contributions(b, is_max=False)
# 计算极差之和
range_sum = 0
for i in range(length):
max_contrib = (i - left_max[i]) * (right_max[i] - i)
min_contrib = (i - left_min[i]) * (right_min[i] - i)
range_sum += b[i] * (max_contrib - min_contrib)
range_sum %= mod
return range_sum
# 测试用例
if __name__ == "__main__":
print(solution(3, [2, 3, 1])) # 输出: 19
print(solution(3, [1, 2, 1])) # 输出: 12
print(solution(4, [1, 1, 1, 1])) # 输出: 4