子数组的最小值之和

21 阅读1分钟

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

907. 子数组的最小值之和 - 力扣(LeetCode)

给定一个整数数组 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 * 10^4
  • 1 <= arr[i] <= 3 * 10^4

思路

本题可以使用暴力解题,就是枚举所有可能的子数组,然后遍历子数组找到最小值,再求和。这种解法虽然好理解,但是浪费空间可时间,不推荐。

本题还可以使用单调栈求解。设arr的长度为narr的子数组最小值和为sum,对于arr[i](0 <= i < n),我们需要计算它对sum的贡献值,即arr[i]在多少个子数组中为最小值,我们可以分别向左和向右遍历,分别找到第一个大于等于arr[i]的值arr[left]arr[right],所有子数组个数为count = (idx - left +1) * (Math.max(right - idx), 1),贡献值为arr[idx] * count。定义stack为单调递减栈,遍历arr,维护一个单调递减的栈,入栈的时候可以计算left的值,但是无法计算right值,故我们在出栈的时候计算leftright,为了统一处理arr[0]arr[n-1],我们在arr头和尾分别添加一个0元素,代码如下。

解题

/**
 * @param {number[]} arr
 * @return {number}
 */
var sumSubarrayMins = function (arr) {
  const MOD = Math.pow(10, 9) + 7;
  arr.unshift(0);
  arr.push(0);
  const n = arr.length;
  const idxs = [0];
  let sum = 0;
  for (let i = 1; i < n; i++) {
    while (arr[idxs.at(-1)] >= arr[i]) {
      const idx = idxs.pop();
      const right = i - idx;
      const left = idxs.length ? idx - idxs.at(-1) : 1;
      sum = (sum + right * left * arr[idx]) % MOD;
    }
    idxs.push(i);
  }
  return sum;
};