小B的极差之和| 豆包MarsCode AI刷题

50 阅读4分钟

问题复现: 小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 个 1a[1] = 3 代表数组 b 中有 3 个 2a[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,要计算所有连续子数组的极差之和。直观的暴力解法是:

  1. 枚举所有子数组。
  2. 计算每个子数组的最大值和最小值。
  3. 求得该子数组的极差并累加。

这种方法的时间复杂度是 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