持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第20天,点击查看活动详情
题目
leetcode 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
题解
暴力做法是枚举所有子数组,算出每个子数组的最小值,但这太慢了。
不妨换个视角,对每个数,算出它是哪些子数组的最小值。
例如arr=[1,4,2,3,1],其中 22 是子数组 [2],[4,2],[2,3],[4,2,3][2],[4,2],[2,3],[4,2,3] 的最小值,那么 2 对答案的贡献就是 2⋅4=8。
由于以 2 为最小值的子数组,绝对不能包含比 2 小的数字,因此我们可以找到 2 左右两侧比它小的数的下标,从而确定子数组的边界。2 对应的边界为开区间 (0,4),即闭区间 [1,3],只要在闭区间 [1,3] 范围内且包含下标 2 的子数组,就是以 2 为最小值的子数组。
var sumSubarrayMins = function(arr) {
const n = arr.length;
let monoStack = [];
const left = new Array(n).fill(0);
const right = new Array(n).fill(0);
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);
}
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;
};
代码详解
[3, 1, 2, 4]从第一个开始往右遍历,遇到小的就替换 [3, 1, 1, 1] [0, 1, 1, 1]从第二个开始 [0, 0, 2, 2]第三 [0, 0, 0, 4]第四 遍历出来每个数组的和就是结果; 以第一个数组为例,实际对应的子数组是[3],[3,1],[3,1,2],[3,1,2,4] 第二个数组是从第二位开始,对应的是[1],[1,2],[1,2,4] ...