持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第30天,点击查看活动详情
最近一直在力扣刷题,也逐渐对各类题型有了自己的理解,所谓见招拆招,将自己的浅显经验分享一下,帮助更多在编程路上的朋友们。
子数组的最小值之和
给定一个整数数组 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 * 1041 <= arr[i] <= 3 * 104
思路
首先,这道题的核心思想是对于每一个元素,都会有其自身作为最小值的区间,我们只需要找到每一个元素作为最小值的区间数,再与该元素的值相乘,即可得到最终结果。
可以将目标区间分为左右两部分,左半部分的子数组个数为 i - left + 1,右半部分的子数组个数为 right - i + 1,二者乘积即为区间总个数,
例如 arr = [3,1,2,4],对于元素3,其作为最小值的区间只有[3],对于元素1来说,i - left + 1 = 2,right - i + 1 = 3,其作为最小值的区间数为6.
这样的话只需要算出每个元素的区间数,再乘该元素的值,将每种情况累加得到答案。
对于每个元素i,我们需要向左找到比arr[i]小的数arr[left]和向右找到比arr[i]大的数arr[right],对于left和right的求解,需要使用单调栈。
题解
class Solution {
public int sumSubarrayMins(int[] arr) {
int MOD = (int) 1e9 + 7;
int n = arr.length;
long sum = 0;
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();
}
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();
}
right[i] = stack.isEmpty()? n: stack.peek();
stack.push(i);
}
for(int i = 0; i < n; i++) {
sum += (long) arr[i] * (i - left[i]) * (right[i] - i);
sum %= MOD;
}
return (int) sum;
}
}