LeetCode探索(164):907-子数组的最小值之和

139 阅读1分钟

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

题目

给定一个整数数组 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,找到 min(b) 的总和,其中 b 的范围为 arr 的每个(连续)子数组。也就是说,比如arr = [3, 1, 2, 4]时,子数组为 [3],[1],[2],[4],[3,1],[1,2],[2,4],[3,1,2],[1,2,4],[3,1,2,4],对这些子数组的最小值累加求和,最终得到结果17。

我们可以借助单调栈解题。

首先,遍历数组 arr,找到以元素arr[i]为最右且arr[i]为最小的子序列的数目left[i],接着,找到以元素arr[i]为最左且arr[i]为最小的子序列的数目right[i]。那么,对于包含arr[i]的子数组,在left[i]right[i]这么多子数组中,最小值都是arr[i],我们可知和为left[i] * right[i] * arr[i]。遍历数组 arr,累加求和即可得到结果。

如何求left[i]呢?新建一个从小到大递增的栈 monoStack。当arr[i] <= 栈顶元素时,弹出栈顶元素,直至 > 栈顶元素或者栈为空。此时,若栈不为空,则 left[i] = i - monoStack[monoStack.length - 1]。求right[i]的过程也类似,最终right[i] = monoStack[monoStack.length - 1] - i

解答

方法一:单调栈

/**
 * @param {number[]} arr
 * @return {number}
 */
var sumSubarrayMins = function(arr) {
  const n = arr.length
  const left = new Array(n).fill(0)
  const right = new Array(n).fill(0)
  // 从小到大递增的栈
  // 找到以元素arr[i]为最右且arr[i]为最小的子序列的数目left[i]
  let monoStack = []
  for (let i = 0; i < n; i++) {
    while (monoStack.length !== 0 && arr[i] <= arr[monoStack[monoStack.length - 1]]) {
      monoStack.pop()
    }
    left[i] = i - (monoStack.length === 0 ? -1 : monoStack[monoStack.length - 1])
    monoStack.push(i)
  }
  
  // 找到以元素arr[i]为最左且arr[i]为最小的子序列的数目right[i]
  monoStack = []
  for (let i = n - 1; i >= 0; i--) {
    while (monoStack.length !== 0 && arr[i] < arr[monoStack[monoStack.length - 1]]) {
      monoStack.pop()
    }
    right[i] = (monoStack.length === 0 ? n : monoStack[monoStack.length - 1]) - i
    monoStack.push(i)
  }

  let ans = 0
  const MOD = 1000000007
  for (let i = 0; i < n; i++) {
    ans = (ans + left[i] * right[i] * arr[i]) % MOD
  }
  return ans
}
// 执行用时:84 ms, 在所有 JavaScript 提交中击败了80.44%的用户
// 内存消耗:49.1 MB, 在所有 JavaScript 提交中击败了47.52%的用户
// 通过测试用例:87 / 87

复杂度分析:

  • 时间复杂度:O(n),其中 n 为数组的长度。
  • 空间复杂度:O(n),其中 n 为数组的长度。

参考