LeetCode 907: Sum of Subarray Minimums (Monotonic Stack Contribution Counting)
LeetCode 907Monotonic StackContributionToday we solve LeetCode 907 - Sum of Subarray Minimums.
Source: https://leetcode.com/problems/sum-of-subarray-minimums/
English
Problem Summary
Given an integer array arr, return the sum of the minimum value of every contiguous subarray. Since the answer may be large, return it modulo 1e9+7.
Key Idea
Instead of enumerating all subarrays, count each element's contribution independently:
For index i, if arr[i] is the minimum for some subarrays, contribution is:
arr[i] * (#choices on left) * (#choices on right)
We need two boundaries:
- prevLess: nearest index on the left with strictly smaller value (
<) - nextLessOrEqual: nearest index on the right with smaller or equal value (
<=)
This tie-breaking (< on left, <= on right) avoids double counting duplicates.
Algorithm
1) Use a monotonic increasing stack to compute prevLess[i].
2) Use another monotonic increasing stack (from right to left) to compute nextLessOrEqual[i].
3) For each index i:
- left = i - prevLess[i]
- right = nextLessOrEqual[i] - i
- add arr[i] * left * right to answer (modulo 1e9+7).
Complexity
Each element is pushed/popped at most once in each stack pass. Time complexity is O(n), and extra space is O(n).
Reference Implementations (Java / Go / C++ / Python / JavaScript)
import java.util.*;
class Solution {
public int sumSubarrayMins(int[] arr) {
int n = arr.length;
int[] prev = new int[n];
int[] next = new int[n];
Deque<Integer> st = new ArrayDeque<>();
final long MOD = 1_000_000_007L;
for (int i = 0; i < n; i++) {
while (!st.isEmpty() && arr[st.peek()] >= arr[i]) st.pop();
prev[i] = st.isEmpty() ? -1 : st.peek();
st.push(i);
}
st.clear();
for (int i = n - 1; i >= 0; i--) {
while (!st.isEmpty() && arr[st.peek()] > arr[i]) st.pop();
next[i] = st.isEmpty() ? n : st.peek();
st.push(i);
}
long ans = 0;
for (int i = 0; i < n; i++) {
long left = i - prev[i];
long right = next[i] - i;
ans = (ans + arr[i] * left % MOD * right) % MOD;
}
return (int) ans;
}
}func sumSubarrayMins(arr []int) int {
const MOD int64 = 1_000_000_007
n := len(arr)
prev := make([]int, n)
next := make([]int, n)
st := make([]int, 0, n)
for i := 0; i < n; i++ {
for len(st) > 0 && arr[st[len(st)-1]] >= arr[i] {
st = st[:len(st)-1]
}
if len(st) == 0 {
prev[i] = -1
} else {
prev[i] = st[len(st)-1]
}
st = append(st, i)
}
st = st[:0]
for i := n - 1; i >= 0; i-- {
for len(st) > 0 && arr[st[len(st)-1]] > arr[i] {
st = st[:len(st)-1]
}
if len(st) == 0 {
next[i] = n
} else {
next[i] = st[len(st)-1]
}
st = append(st, i)
}
var ans int64 = 0
for i := 0; i < n; i++ {
left := int64(i - prev[i])
right := int64(next[i] - i)
ans = (ans + int64(arr[i])*left%MOD*right) % MOD
}
return int(ans)
}class Solution {
public:
int sumSubarrayMins(vector<int>& arr) {
const long long MOD = 1'000'000'007LL;
int n = (int)arr.size();
vector<int> prev(n), next(n);
vector<int> st;
st.reserve(n);
for (int i = 0; i < n; ++i) {
while (!st.empty() && arr[st.back()] >= arr[i]) st.pop_back();
prev[i] = st.empty() ? -1 : st.back();
st.push_back(i);
}
st.clear();
for (int i = n - 1; i >= 0; --i) {
while (!st.empty() && arr[st.back()] > arr[i]) st.pop_back();
next[i] = st.empty() ? n : st.back();
st.push_back(i);
}
long long ans = 0;
for (int i = 0; i < n; ++i) {
long long left = i - prev[i];
long long right = next[i] - i;
ans = (ans + arr[i] * left % MOD * right) % MOD;
}
return (int)ans;
}
};class Solution:
def sumSubarrayMins(self, arr):
MOD = 10**9 + 7
n = len(arr)
prev = [-1] * n
nxt = [n] * n
st = []
for i, v in enumerate(arr):
while st and arr[st[-1]] >= v:
st.pop()
prev[i] = st[-1] if st else -1
st.append(i)
st.clear()
for i in range(n - 1, -1, -1):
while st and arr[st[-1]] > arr[i]:
st.pop()
nxt[i] = st[-1] if st else n
st.append(i)
ans = 0
for i, v in enumerate(arr):
left = i - prev[i]
right = nxt[i] - i
ans = (ans + v * left * right) % MOD
return ansvar sumSubarrayMins = function(arr) {
const MOD = 1000000007n;
const n = arr.length;
const prev = new Array(n).fill(-1);
const next = new Array(n).fill(n);
const st = [];
for (let i = 0; i < n; i++) {
while (st.length && arr[st[st.length - 1]] >= arr[i]) st.pop();
prev[i] = st.length ? st[st.length - 1] : -1;
st.push(i);
}
st.length = 0;
for (let i = n - 1; i >= 0; i--) {
while (st.length && arr[st[st.length - 1]] > arr[i]) st.pop();
next[i] = st.length ? st[st.length - 1] : n;
st.push(i);
}
let ans = 0n;
for (let i = 0; i < n; i++) {
const left = BigInt(i - prev[i]);
const right = BigInt(next[i] - i);
ans = (ans + BigInt(arr[i]) * left % MOD * right) % MOD;
}
return Number(ans);
};中文
题目概述
给定整数数组 arr,要求计算所有连续子数组最小值之和,并对 1e9+7 取模。
核心思路
不要枚举所有子数组,而是统计“每个元素作为最小值时被多少个子数组选中”。
对位置 i 来说,贡献是:
arr[i] * 左侧可选数量 * 右侧可选数量
边界定义如下:
- prevLess:左边最近一个严格小于
arr[i]的位置 - nextLessOrEqual:右边最近一个小于等于
arr[i]的位置
这样设计可以稳定处理重复值,避免重复计数。
算法步骤
1)单调递增栈从左到右,求每个位置左边最近更小元素。
2)单调递增栈从右到左,求每个位置右边最近小于等于元素。
3)枚举每个下标 i:
- left = i - prevLess[i]
- right = nextLessOrEqual[i] - i
- 累加 arr[i] * left * right(取模)。
复杂度分析
每个元素在两次栈扫描中最多入栈出栈各一次,总时间复杂度 O(n),额外空间复杂度 O(n)。
参考实现(Java / Go / C++ / Python / JavaScript)
import java.util.*;
class Solution {
public int sumSubarrayMins(int[] arr) {
int n = arr.length;
int[] prev = new int[n];
int[] next = new int[n];
Deque<Integer> st = new ArrayDeque<>();
final long MOD = 1_000_000_007L;
for (int i = 0; i < n; i++) {
while (!st.isEmpty() && arr[st.peek()] >= arr[i]) st.pop();
prev[i] = st.isEmpty() ? -1 : st.peek();
st.push(i);
}
st.clear();
for (int i = n - 1; i >= 0; i--) {
while (!st.isEmpty() && arr[st.peek()] > arr[i]) st.pop();
next[i] = st.isEmpty() ? n : st.peek();
st.push(i);
}
long ans = 0;
for (int i = 0; i < n; i++) {
long left = i - prev[i];
long right = next[i] - i;
ans = (ans + arr[i] * left % MOD * right) % MOD;
}
return (int) ans;
}
}func sumSubarrayMins(arr []int) int {
const MOD int64 = 1_000_000_007
n := len(arr)
prev := make([]int, n)
next := make([]int, n)
st := make([]int, 0, n)
for i := 0; i < n; i++ {
for len(st) > 0 && arr[st[len(st)-1]] >= arr[i] {
st = st[:len(st)-1]
}
if len(st) == 0 {
prev[i] = -1
} else {
prev[i] = st[len(st)-1]
}
st = append(st, i)
}
st = st[:0]
for i := n - 1; i >= 0; i-- {
for len(st) > 0 && arr[st[len(st)-1]] > arr[i] {
st = st[:len(st)-1]
}
if len(st) == 0 {
next[i] = n
} else {
next[i] = st[len(st)-1]
}
st = append(st, i)
}
var ans int64 = 0
for i := 0; i < n; i++ {
left := int64(i - prev[i])
right := int64(next[i] - i)
ans = (ans + int64(arr[i])*left%MOD*right) % MOD
}
return int(ans)
}class Solution {
public:
int sumSubarrayMins(vector<int>& arr) {
const long long MOD = 1'000'000'007LL;
int n = (int)arr.size();
vector<int> prev(n), next(n);
vector<int> st;
st.reserve(n);
for (int i = 0; i < n; ++i) {
while (!st.empty() && arr[st.back()] >= arr[i]) st.pop_back();
prev[i] = st.empty() ? -1 : st.back();
st.push_back(i);
}
st.clear();
for (int i = n - 1; i >= 0; --i) {
while (!st.empty() && arr[st.back()] > arr[i]) st.pop_back();
next[i] = st.empty() ? n : st.back();
st.push_back(i);
}
long long ans = 0;
for (int i = 0; i < n; ++i) {
long long left = i - prev[i];
long long right = next[i] - i;
ans = (ans + arr[i] * left % MOD * right) % MOD;
}
return (int)ans;
}
};class Solution:
def sumSubarrayMins(self, arr):
MOD = 10**9 + 7
n = len(arr)
prev = [-1] * n
nxt = [n] * n
st = []
for i, v in enumerate(arr):
while st and arr[st[-1]] >= v:
st.pop()
prev[i] = st[-1] if st else -1
st.append(i)
st.clear()
for i in range(n - 1, -1, -1):
while st and arr[st[-1]] > arr[i]:
st.pop()
nxt[i] = st[-1] if st else n
st.append(i)
ans = 0
for i, v in enumerate(arr):
left = i - prev[i]
right = nxt[i] - i
ans = (ans + v * left * right) % MOD
return ansvar sumSubarrayMins = function(arr) {
const MOD = 1000000007n;
const n = arr.length;
const prev = new Array(n).fill(-1);
const next = new Array(n).fill(n);
const st = [];
for (let i = 0; i < n; i++) {
while (st.length && arr[st[st.length - 1]] >= arr[i]) st.pop();
prev[i] = st.length ? st[st.length - 1] : -1;
st.push(i);
}
st.length = 0;
for (let i = n - 1; i >= 0; i--) {
while (st.length && arr[st[st.length - 1]] > arr[i]) st.pop();
next[i] = st.length ? st[st.length - 1] : n;
st.push(i);
}
let ans = 0n;
for (let i = 0; i < n; i++) {
const left = BigInt(i - prev[i]);
const right = BigInt(next[i] - i);
ans = (ans + BigInt(arr[i]) * left % MOD * right) % MOD;
}
return Number(ans);
};
Comments