leetcode 907. 子数组的最小值之和
xxleyi opened this issue · 0 comments
xxleyi commented
题:
给定一个整数数组 A,找到 min(B) 的总和,其中 B 的范围为 A 的每个(连续)子数组。
由于答案可能很大,因此返回答案模 10^9 + 7。
示例:
输入:[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。
提示:
1 <= A <= 30000
1 <= A[i] <= 30000
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/sum-of-subarray-minimums
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解:此题暴力解法不难,难得是线性复杂度条件下解决问题。
// 暴力求解,临近超时边缘
var sumSubarrayMins = function(A) {
const bigPrimeNumber = 10 ** 9 + 7
// 「循环不变式」所需要的变量
let stackTop = 0
// 存储答案
let ans = 0
for (let i = 0; i < A.length; i++) {
for (let j = i; j < A.length; j++) {
// 子数组长度为 1 时,将子数组首元素直接入栈
if (j === i) {
stackTop = A[j]
// 子数组长度大于 1 时,将子数组的最小值入栈
} else {
stackTop = Math.min(stackTop, A[j])
}
// 更新 ans
ans += stackTop
}
}
// 循环终止,返回答案
return ans % bigPrimeNumber
};
下面这个线性复杂度解法是利用单调递增栈,总共需要维护 四个「循环不变式」 相关的变量。一般题目也就两三个,此题一共四个,而且思路巧妙。突破点是意识到对数组中的每一个元素进行计数,然后利用单调递增栈更新和维护计数,同时累计子数组最小元素之和。
// 线性复杂度:单调递增栈
var sumSubarrayMins = function(A) {
const bigPrimeNumber = 10 ** 9 + 7
// 外层 for「循环不变式」相关的变量
const stack = []
let minSoFar = 0
let subMinSoFar = 0
for (let y of A) {
// 内层 while「循环不变式」相关的变量,初始值为 1
let countY = 1
// 更新单调递增栈,保证「循环不变式」
while (stack.length && stack[stack.length - 1][0] >= y) {
// 弹出不再单调递增的部分
const [x, c] = stack.pop()
// 更新 countY: 弹出的 count 都应该归到当前元素 y 上
countY += c
// 更新 subMinSoFar: 将出栈部分剔除
subMinSoFar -= x * c
}
// 内层 while 循环终止:将当前元素以及对应的 count 入栈
stack.push([y, countY])
// 更新 subMinSoFar: 计入入栈部分
subMinSoFar += y * countY
// 将当前稳定的 subMinSoFar 计入到总的 minSoFar
minSoFar += subMinSoFar
}
// 外层循环终止:对大素数取模之后得到目标解
return minSoFar % bigPrimeNumber
}
补充前驱递减数组(使用正向递增栈求解)和后继递增数组(使用反向递增栈求解)的方法
// Prev / Next Array
var sumSubarrayMins = function(A) {
const bigPrimeNumber = 10 ** 9 + 7
// 数组的左右边界
const [lo, hi] = [-1, A.length]
// 从左到右递增栈(记录索引)
const stack = []
const prev = Array(hi).fill(lo)
for (let i = 0; i < hi; i++) {
while (stack.length && A[i] <= A[stack[stack.length - 1]]) stack.pop()
if (stack.length) prev[i] = stack[stack.length - 1]
stack.push(i)
}
// 从右到左递增栈(记录索引)
stack.length = 0
const next = Array(hi).fill(hi)
for (let k = hi - 1; k >= 0; k--) {
while (stack.length && A[k] < A[stack[stack.length - 1]]) stack.pop()
if (stack.length) next[k] = stack[stack.length - 1]
stack.push(k)
}
// sum{A[i] * (left[i] + 1) * (right[i] + 1)} (lo < i < hi)
let min = 0
for (let i = lo + 1; i < hi; i++) {
min += (i - prev[i]) * (next[i] - i) * A[i]
}
return min % bigPrimeNumber
}