题目描述
给定一个整数数组 arr
,找到 min(b)
的总和,其中 b
的范围为 arr
的每个(连续)子数组。
由于答案可能很大,因此 返回答案模 10^9 + 7
。
样例
输入: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。
输入:arr = [11,81,94,43,3]
输出:444
注意
1 <= arr.length <= 3 * 10^4
1 <= arr[i] <= 3 * 10^4
算法
(单调栈) $O(n)$
- 转变思路,统计每个位置的数字贡献的答案是多少。
- 对于每个位置
i
,求出最大的l[i]
,满足A[l[i]] <= A[i]
,如果不存在则l[i] = -1
; - 同理,求出最小
r[i]
,满足A[r[i]] < A[i]
,如果不存在则r[i] = n
; - 这样,每个位置贡献的答案就是,
A[i] * (r[i] - i) * (i - l[i])
。 - 求
l[i]
和r[i]
可以用单调栈在线性时间内完成。 - 具体地,维护一个单调递增(不降)的栈,每次新插入数字前,比较栈顶,如果不满足要求则栈顶出栈,并更新对应的
l[i] (r[i])
数组即可;最后当前数字的下标入栈。
时间复杂度
- 仅遍历数组若干次,故时间复杂度为 $O(n)$。
空间复杂度
- 需要额外的栈和数组,故空间复杂度为 $O(n)$。
C++ 代码
#define LL long long
class Solution {
public:
int sumSubarrayMins(vector<int>& arr) {
const int n = arr.size();
const int mod = 1000000007;
stack<int> s;
vector<int> l(n), r(n);
for (int i = 0; i < n; i++) {
while (!s.empty() && arr[i] < arr[s.top()]) {
r[s.top()] = i;
s.pop();
}
s.push(i);
}
while (!s.empty()) {
r[s.top()] = n;
s.pop();
}
for (int i = n - 1; i >= 0; i--) {
while (!s.empty() && arr[i] <= arr[s.top()]) {
l[s.top()] = i;
s.pop();
}
s.push(i);
}
while (!s.empty()) {
l[s.top()] = -1;
s.pop();
}
LL ans = 0;
for (int i = 0; i < n; i++)
ans = (ans + (LL)(arr[i]) * (r[i] - i) * (i - l[i])) % mod;
return ans;
}
};
我觉得这种做法最有意思的是在维护两个方向的单调栈时,一个单调栈使用小于号,另一个单调栈使用的是小于等于号。这种做法完美的解决了重复元素的重复计数问题。
hhh,是的
这种一个小于一个小于等于是挺完美的解决了重复问题,可是很难想到
每个位置贡献的答案是不是和LC.828的结果思想一样
对的