每日一题

52 阅读1分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第28天,点击查看活动详情

907. 子数组的最小值之和

给定一个整数数组 arr,找到 min(b) 的总和,其中 b 的范围为 arr 的每个(连续)子数组。

由于答案可能很大,因此 返回答案模 10^9 + 7 。

 

示例 1:

输入: arr = [3,1,2,4]
输出: 17
解释: 子数组为 [3][1][2][4][3,1][1,2][2,4][3,1,2][1,2,4][3,1,2,4]。 
最小值为 3,1,2,4,1,1,2,1,1,1,和为 17。

示例 2:

输入: arr = [11,81,94,43,3]
输出: 444

 

提示:

  • 1 <= arr.length <= 3 * 104
  • 1 <= arr[i] <= 3 * 104

解题思路

主要解题方法:单调栈

  1. 我们要利用单调栈找出每一个元素的左右边界
    • 即向左向右扩张的最远距离
    • 使得在这个子数组中,最小值为该元素本身。
  2. 根据步骤1我们可以计算出对于这样一个最大的子数组,它所有的包含当前元素的子数组有多少个。

然后计算每一个元素对于答案的贡献。

  • 需要注意,在求右边界的时候单调栈不是严格递增的
  • 也就是对于当前元素与栈顶元素相等的情况,我们需要往右扩张
  • 因为即使两个同样最小的元素在同一个子数组中,它的最小值也是这两个元素
  • 我们需要且只能计算一次,所以在求左右边界的时候,出栈的判断条件,一个需要包括相等,另一个不需要包括相等。
  • 然后再遍历一次,利用左右边界可以求出以当前元素为最小值的所有连续子数组的个数,继而可以求出每个元素对答案的贡献值。

实现:

/**
 * @param {number[]} arr
 * @return {number}
 */
var sumSubarrayMins = function(arr) {
  // 维护一个严格单调递增的栈,栈中存下标
  const stack = [];
  const left = [];
  const right = [];
  const len = arr.length;
  const mod = 1_000_000_007;
  let ans = 0;
  for(let i = 0; i < len; i++) {
    // 一个需要等于
    while(stack.length && arr[stack[stack.length-1]] >= arr[i]) {
      stack.pop();
    }
    const top = stack[stack.length-1] ?? -1;
    stack.push(i);
    left.push(top + 1);
  }
  stack.length = 0;
  for(let i = len-1; i >= 0; i--) {
    // 一个不能等于
    while(stack.length && arr[stack[stack.length-1]] > arr[i]) {
      stack.pop();
    }
    const top = stack[stack.length-1] ?? len;
    stack.push(i);
    right.push(top - 1);
  }