题目描述
给定一个整数数组
找到 min(b)
的总和
其中 b
的范围为 arr
的每个(连续)子数组。
由于答案可能很大,因此 返回答案模
示例 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
提示:
题目解析
- 直观地,遍历拿到每一个子数组的最小值然后相加得到最终结果
- 很显然这样时间复杂度太高了
- 由于只需要求每一个连续子数组的最小值,每一个元素值考虑和其后面的元素相比较即可
- 以数组
arr = [3, 1, 2, 4]
为例,有四个元素 - 以元素
3
为中心的话,选取子数组,保证元素3
最小,则一共有一种方案 ,即{3}
- 因为元素
3
最小的子数组有且仅有{3}
- 以元素
1
为最小值的子数组的最长连续子数组是{3, 1, 2, 4}
- 所有的子数组,元素
1
都是最小值 - 和 = 子数组个数 *
1
- 那么问题变成了如何计算子数组个数
- 所有的子数组有
{3,1},{3,1,2},{3,1,2,4},{1},{1,2},{1,2,4}
一共有6
个 - 可以得到左右边界以及当前元素的下标
- 因此有:子数组个数 = (当前元素下标 - 左边界下标 + 1)* (右边界下标 - 当前元素下标 + 1)
- 记当前元素下标为
- 以为最小值的左边界下标为
- 以为最小值的右边界下标为
show code
class Solution {
public int sumSubarrayMins(int[] arr) {
int n = arr.length;
// 创建一个数组 代表以当前元素为最小元素的左边界范围
int[] left = new int[n];
// 右边界数组
int[] right = new int[n];
// 创建一个栈辅助找到最小值
Deque<Integer> stack = new LinkedList<>();
for(int i = 0;i < n;i++) {
// 这里用大于 等于 判断:表示左边界去包含相等元素
while(!stack.isEmpty() && arr[stack.peek()] >= arr[i]) {
stack.pop();
}
// 计算左边界的长度范围
left[i] = 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()) - i;
stack.push(i);
}
long ans = 0;
int mod = 1000000007;
// 计算最终结果.
for(int i = 0;i < n;i++) {
ans = (ans + (long)left[i] * right[i] * arr[i]) % mod;
}
return (int) ans;
}
}