思路
实际上是要求
res = sum(A[i] * f(i))
,其中f(i)
是子数组的数量,A[i]
是最小值。
为了得到f(i)
,我们要得到
-
left[i]
,能在A[i]
左侧取得的最长宽度,使得在这个范围内A[i]
是最小值,且都大于A[i]
-
right[i]
, 能在A[i]
右侧取得的最长宽度,使得在这个范围内A[i]
是最小值,且都大于等于A[i]
然后,
-
A[i]
左侧有left[i] + 1
种连续子数组的取法 -
A[i]
右侧有right[i] + 1
种连续子数组的取法
最后根据f(i) = (left[i] + 1) * (right[i] + 1)
即可求得f(i)
所以这个问题在于求left[i]
和right[i]
举例,对于[3,1,2,4]
:
left + 1 = [1,2,1,1]
right + 1 = [1,3,2,1]
f = [1,6,2,1]
res = 3 * 1 + 1 * 6 + 2 * 2 + 4 * 1 = 17
那么,解释一下为什么left[i]
要取大于A[i]
,而right[i]
只需要取大于等于A[i]
:
他是为了解决数组中有相等的数的情况。
假设有数组...a1...a2...a3... ...an...
其中,a1=a2=a3=...=an
,那么这个连续数组的各种组合中,起点可以起自...a1
, ...a2
, ...a3
,直到...an
,而终点则可以尽可能地往右边取。
所以在i=a1
处,left[i]
最多取到a1
,而在i=a2
处,left[i]
最少也取到a1+1
。如此类推,起点可以从...a1...a2
一直取到an
,而终点可以从起点一直取到最右侧(只要大于等于起点的值),如此可以把全部的连续子数组的情况覆盖。
算法
那么怎么求得left[i]
和right[i]
呢?我们可以看以下两个同类型的问题的讨论:
- LC901. Online Stock Span
- LC84. Largest Rectangle in Histogram
答案主要有两种做法,一种是用栈,一种是用数组。
解决方案1:栈
[C++/Java/Python] O(1)
就是用increasing stack来解决问题。
解决方案2:数组
5ms O(n) Java solution explained (beats 96%)
本来遍历要一个一个遍历的,像这样:
for (int i = 1; i < height.length; i++) {
int p = i - 1;
while (p >= 0 && height[p] >= height[i]) {
p--;
}
lessFromLeft[i] = p;
}
但我们可以改良它,在往前遍历时不一个一个地遍历,而是利用之前的计算结果,直接“跳”过多个元素:
while (p >= 0 && height[p] >= height[i]) {
p = lessFromLeft[p];
}