方法一:模拟
考虑所有满足以数组 a r r arr arr 中的某个元素 a r r [ i ] arr[i] arr[i] 为最右且最小的元素的子序列个数 C [ i ] C[i] C[i],那么题目所求连续子数组的最小值之和即为 ∑ i = 0 n − 1 a r r [ i ] × C [ i ] \sum_{i=0}^{n-1}arr[i]\times C[i] ∑i=0n−1arr[i]×C[i],其中数组 a r r arr arr 的长度为 n n n。我们必须假设当前元素为最右边且最小的元素,这样才可以构造互不相交的子序列,否则会出现多次计算,因为一个数组的最小值可能不唯一。
经过以上思考,我们只需要找到每个以 a r r [ i ] arr[i] arr[i] 为最右且最小的子序列的数目 l e f t [ i ] left[i] left[i] 和以 a r r [ i ] arr[i] arr[i] 为最左且最小的子序列的数目 r i g h t [ i ] right[i] right[i],则以 a r r [ i ] arr[i] arr[i] 为最小元素的子序列的数目合计为 l e f t [ i ] × r i g h t [ i ] left[i]\times right[i] left[i]×right[i]。
为了防止重复计算,我们可以设 a r r [ i ] arr[i] arr[i] 左边的元素都必须大于等于 a r r [ i ] arr[i] arr[i], a r r [ i ] arr[i] arr[i] 右边的元素必须严格小于 a r r [ i ] arr[i] arr[i]。这就变成了求最小的下标 j ≤ i j\le i j≤i,且连续子序列中的元素 a r r [ j ] , a r r [ j + 1 ] , ⋯ , a r r [ i ] arr[j],arr[j+1],\cdots,arr[i] arr[j],arr[j+1],⋯,arr[i] 都大于等于 a r r [ i ] arr[i] arr[i];以及求最大的下标 k > i k > i k>i 满足连续子序列 a r r [ i ] , a r r [ i + 1 ] , ⋯ , a r r [ k ] arr[i],arr[i+1],\cdots,arr[k] arr[i],arr[i+1],⋯,arr[k] 都严格大于 a r r [ i ] arr[i] arr[i]。
class Solution {
public final int MOD = 1000000007;
public int sumSubarrayMins(int[] arr) {
long sum = 0;
for(int i = 0; i < arr.length; i++){
int left = 1, right = 1;
while(left <= i && arr[i] < arr[i - left]){ //以当前元素为最小右边界的连续子序列个数
left++;
}
while((i + right) < arr.length && arr[i] <= arr[i + right]){//以当前元素为最小左边界的连续子序列个数
right++;
}
sum = (sum + (long)arr[i] * left * right) % MOD; //当前元素对总和的贡献
}
return (int)sum;
}
}
方法二:单调栈
上述即转化为经典的单调栈问题,即求数组中当前元素 x x x 左边第一个小于 x x x 的元素以及右边第一个小于等于 x x x 的元素。
对于数组中每个元素 a r r [ i ] arr[i] arr[i],具体做法如下:
- 求左边第一个小于 a r r [ i ] arr[i] arr[i] 的元素:从左向右遍历数组,并维护一个单调递增的栈,遍历当前元素 a r r [ i ] arr[i] arr[i],如果遇到当前栈顶的元素大于等于 a r r [ i ] arr[i] arr[i] 则将其弹出,直到栈顶的元素小于 a r r [ i ] arr[i] arr[i],栈顶的元素即为左边第一个小于 a r r [ i ] arr[i] arr[i] 的元素 a r r [ j ] arr[j] arr[j],此时 l e f t [ i ] = i − j left[i]=i-j left[i]=i−j。
- 求右边第一个小于等于 a r r [ i ] arr[i] arr[i] 的元素:从右向左遍历数组,维护一个单调递增的栈,遍历当前元素 a r r [ i ] arr[i] arr[i],如果遇到当前栈顶的元素大于 a r r [ i ] arr[i] arr[i] 则将其弹出,直到栈顶的元素小于等于 a r r [ i ] arr[i] arr[i],栈顶的元素即为右边第一个小于等于 a r r [ i ] arr[i] arr[i] 的元素 a r r [ k ] arr[k] arr[k],此时 r i g h t [ i ] = k − i right[i]=k-i right[i]=k−i。
- 连续子数组 a r r [ j ] , a r r [ j + 1 ] , ⋯ , a r r [ k ] arr[j],arr[j+1],\cdots,arr[k] arr[j],arr[j+1],⋯,arr[k] 的最小元素即为 a r r [ i ] arr[i] arr[i],以 a r r [ i ] arr[i] arr[i] 为最小元素的连续子序列的数量为 ( i − j ) × ( k − i ) (i-j)\times(k-i) (i−j)×(k−i)。
根据以上结论可以知道,所有子数组的最小值之和即为 ∑ i = 0 n − 1 a r r [ i ] × l e f t [ i ] × r i g h t [ i ] \sum_{i=0}^{n-1}arr[i]\times left[i]\times right[i] ∑i=0n−1arr[i]×left[i]×right[i]。维护单调栈的过程是线性的。
class Solution {
public final int MOD = 1000000007;
public int sumSubarrayMins(int[] arr) {
int n = arr.length;
long ans = 0;
Deque<Integer> monoStack = new ArrayDeque<Integer>();
int[] left = new int[n];
int right = 0;
for (int i = 0; i < n; i++) { //求左边第一个小于arr[i]的元素,等于计算,防止漏掉最小值不唯一的情况
while (!monoStack.isEmpty() && arr[i] <= arr[monoStack.peek()]) {
monoStack.pop();
}
left[i] = i - (monoStack.isEmpty() ? -1 : monoStack.peek());
monoStack.push(i);
}
monoStack.clear();
for (int i = n - 1; i >= 0; i--) { //求右边第一个小于等于arr[i]的元素,等于不计算,防止重复计算
while (!monoStack.isEmpty() && arr[i] < arr[monoStack.peek()]) {
monoStack.pop();
}
right = (monoStack.isEmpty() ? n : monoStack.peek()) - i;
ans = (ans + (long) left[i] * right * arr[i]) % MOD; //求以arr[i]为最小元素的贡献值
monoStack.push(i);
}
return (int) ans;
}
}