题目分析
给定一个数组A,求该数组的所有连续子数组的最小值的总和。
举个例子:比如A=[3,1,2,4],所有连续子数组分别为 [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。
链接:https://leetcode.cn/problems/sum-of-subarray-minimums/solution/xiao-bai-lang-dong-hua-xiang-jie-bao-zhe-489q/
来源:力扣(LeetCode)
解题思路:
最小值是在一段连续数字中被筛选出来的,也就是说每个最小值都有一定的辐射范围。假设给定数组A=[3,1,2,4,1],在一段连续数字3、1、2、4、1中,只要其中一段数字包含1,那么这段数字的最小值肯定为1,例如[3,1,2,4,1]、[3,1,2,4]、[3,1,2]、[1,2]等最小值都为1,我们把这叫做元素1的辐射范围。
假设辐射范围的左边界为left,右边界为right,元素E的下标为i,那么子数组的左边界应该在[left,i]中选取,子数组的右边界应该在[i,right]中选取。因此子数组个数为(i - left + 1) * (right - i + 1)(i−left+1)∗(right−i+1),也就是说元素A[i]对答案的总贡献值为A[i]*(i - left + 1) * (right - i + 1)A[i]∗(i−left+1)∗(right−i+1)。
代码:(Java)
public static class Solution {
private static final int mod =1000000007;
public int sumSubarrayMins(int[] arr) {
if(arr==null||arr.length==0) {
return 0;
}
int n = arr.length;
int left[] = new int[n];
int right[] = new int[n];
Deque<Integer> stack = new LinkedList<Integer>();
//求左边界
for(int i=0;i<n;i++) {//左边栈中一般存放的是当前第一小的与第二小的
while(!stack.isEmpty()&&arr[stack.peek()]>arr[i]) {
stack.pop(); //找到左边比当前数更小的,大的都出栈
}
if(stack.isEmpty()) {//设置边界,左边没有更小的就设为-1
left[i] = -1;
}else {
left[i] = stack.peek();//找到更小的
}
stack.push(i); //将当前值放入栈,方便得到i与arr[i]
}
stack.clear();//清空栈
for(int i =n-1;i>=0;i--) {
//System.out.println(stack.size());
while(!stack.isEmpty()&&arr[stack.peek()]>=arr[i]) {
stack.pop();
}
if(stack.isEmpty()) {
right[i] =n;
}else {
right[i] =stack.peek();
}
stack.push(i);
}
long ans = 0;
for(int i=0;i<n;i++) {
ans=(ans+(long)(i-left[i])*(right[i]-i)*arr[i])%mod;
}
return (int) ans;
}