持续创作,加速成长!这是我参与「掘金日新计划 · 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^41 <= 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 为数组的长度。