Leetcode 907. 子数组的最小值之和
基本思路:
找到每个数能“管辖的区域”,计算
∑
i
=
0
l
e
n
−
1
a
r
r
[
i
]
×
区
域
个
数
\sum_{i=0}^{len-1} arr[i]\times区域个数
∑i=0len−1arr[i]×区域个数。因为有很多子数组的 min 是一样的,所以不用遍历 arr 中的所有子数组,然后求最小值,而是反过来,把所有可能的最小值,也就是把 arr[i]
从 0 到 len - 1 遍历一遍,计算 arr[i] * 对应数组个数
就好。
现在的问题就是,如何计算每个 arr[i]
对应的数组个数?
简单分析一下,arr[i]
对应的数组,其最小值是 arr[i]
,也就是说其余值都比 arr[i]
大。这非常符合单调栈的思路。
所谓单调栈,就是栈中的元素满足单调递增 / 递减的顺序。至于单调栈如何构建,这里不做说明,请自行查找资料。
单调栈,重点不是在于最终栈中保存了什么内容,而是在于:元素为什么能入栈,为什么能出栈?以递增栈为例,入栈是因为前面那个元素比它小,而出栈是因为后面的那个元素也比它小。这样就找到了某个元素左右两边最近的比它小的两个数。
换言之,对于 arr[i]
,若在左右两边分别找到了最近的比它小的数,左边为 arr[l - 1]
,右边为 arr[r + 1]
,那么我们也就找到了一个区间 arr[l] ~ arr[r]
,这个区间中,除了 arr[i] 外,其它所有元素都比 arr[i]
大。
说得稍微形象一些,就是包含这个低点的最大峰谷。也就是所谓的“管辖区域”了。
找到管辖区域 [l, r] 后,根据乘法原理,满足 min 为 arr[i]
子数组个数,为 (i - l + 1) * (r - i + 1)。这样就求到了最小值为 arr[i]
情况下的和。
当然,最小值有很多,从 arr[0]
, arr[1]
, arr[2]
… arr[n - 1]
,只要在 arr 数组中就会成为最小值。然后把所有的最小值和对应的数组个数相乘并求和就好了。也就是一开始的那个公式:
∑
i
=
0
l
e
n
−
1
a
r
r
[
i
]
×
区
域
个
数
\sum_{i=0}^{len-1} arr[i]\times区域个数
∑i=0len−1arr[i]×区域个数。
class Solution {
int mod = 1000000007;
public int sumSubarrayMins(int[] arr) {
Stack<Integer> stack = new Stack<>();
int res = 0;
for(int i = 0; i < arr.length; i++) {
while(!stack.isEmpty() && arr[i] < arr[stack.peek()]) {
int idx = stack.pop();
int begin = 0;
if(!stack.isEmpty()) {
begin = stack.peek() + 1;
}
res = (int)((res + (long) arr[idx] * (idx - begin + 1) * (i - idx)) % mod);
}
stack.push(i);
}
// System.out.println(res + " " + stack.peek());
while(!stack.isEmpty()) {
int idx = stack.pop();
int begin = 0;
if(!stack.isEmpty()) {
begin = stack.peek() + 1;
}
res = (int)((res + (long) arr[idx] * (idx - begin + 1) * (arr.length - idx)) % mod);
}
return res;
}
}