题目来源:https://leetcode.cn/problems/sum-of-subarray-minimums/
大致题意:
给一个数组,求数组所有子数组的最小值的和
思路
如果直接枚举所有子数组,然后统计最小值的和会超时
这一类统计子数组最值的题可以使用单调栈来解决,即使用单调栈求出每个元素作为最值的子数组的边界
如该题,使用单调栈求出第 i 个位置元素为最小值的子数组边界为 (l, r)该区间为开区间,即不包含 l 和 r
,那么该边界中子数组的个数即为 (r - i) * (i - l),也就是有 (r - i) * (i - l) 个子数组最小值为第 i 个位置元素,于是第 i 个位置对整体答案的贡献即为 nums[i] * (r - i) * (i - l)
单调栈
- 使用单调栈求出数组中每个元素左侧小于它的第一个元素位置 l (如果没有则为 -1),和右侧小于等于它的第一个元素位置 r(如果没有则为数组长度),于是 (l, r) 内即为当前元素作为最小值的所有子数组
左侧小于,右侧小于等于是为了防止出现区间重合的情况
如 [3, 1, 2, 1, 3],如果两侧都是寻找小于当前元素的位置,那么两个 1 找到的边界都会为 (-1, 5),出现重合,会重复统计子数组的个数。如果左侧小于,右侧小于等于,那么第一个 1 统计的边界为 (-1, 3),第二个 1 统计的边界为 (-1, 5)
- 根据边界值求出每个元素对答案的贡献,统计结果
public int sumSubarrayMins(int[] arr) {
int n = arr.length;
// 所有位置的左边界
int[] left = new int[n];
// 所有位置的右边界
int[] right = new int[n];
// 单调栈
Deque<Integer> stack = new ArrayDeque<>();
for (int i = 0; i < n; i++) {
// 在单调栈中第一个小于当前元素的值(大于等于不满足,弹出栈)
while (!stack.isEmpty() && arr[stack.peek()] >= arr[i]) {
stack.pop();
}
// 如果单调栈为空,表示之前出现的元素没有小于当前元素的,左边界值为 -1
// 否则,左边界值即为对应元素位置
left[i] = stack.isEmpty() ? -1 : stack.peek();
stack.push(i);
}
stack.clear();
for (int i = n - 1; i >= 0; i--) {
// 在单调栈中第一个小于等于当前元素的值(大于不满足,弹出栈)
while (!stack.isEmpty() && arr[stack.peek()] > arr[i]) {
stack.pop();
}
// 如果单调栈为空,表示之前出现的元素没有小于等于当前元素的,右边界值为 n
// 否则,右边界值即为对应元素位置
right[i] = stack.isEmpty() ? n : stack.peek();
stack.push(i);
}
int MOD = 1000000007;
long ans = 0;
for (int i = 0; i < n; i++) {
// 当前数组作为最小值的子数组个数
int count = (right[i] - i) * (i - left[i]);
// 统计贡献值
ans = (ans + (long) count * arr[i] % MOD) % MOD;
}
return (int) ans;
}