持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第29天,点击查看活动详情
单调栈是一个存储拥有单调性的序列的栈式结构,一般可以使用其完成某些上升或下降的问题。 本文,就介绍一下,单调栈如何来优化时间复杂度。
子数组的最小值之和
给定一个整数数组 arr,找到 min(b) 的总和,其中 b 的范围为 arr 的每个(连续)子数组。
数据范围
1 <= arr.length <= 3 * 10^41 <= arr[i] <= 3 * 10^4
解法
其实数据范围挺小的,稍不注意,我暴力都可以过。
可以再丧心病狂一点,把数据拉到 级别
极致的暴力做法()
我们可以枚举这个序列左右端点,枚举左右端点的时间复杂度为
枚举完每一个序列左右端点后,再对其区间求 操作,总体时间复杂度
所以叫做极致的暴力
暴力做法()
想一想,有必要去枚举左右端点么?
换一个思路,去寻找当前这个值可以管的左边最长的和右边最长的是多少。
去寻找每一个区间的左右最长延伸,所耗费的时间复杂度为
枚举 n 个值,总时间复杂度为
单调栈()
其实吧,预处理每一个区间的 这一件事情,我们可以做的更好!
这个地方就是单调栈的用途所在了,我们处理了n次 区间,试问,真的没有重复循环的地方吗?
举个例子:
1,4, 3, 2, 1
对于它来说,4,3,2,1的左区间定值都没有任何变化,就是1!
这里就是重复计算的地方。
可以想个方法来预处理这n个操作。
一个比较好的办法是维护一个单调递增的序列,若是 当前的 是小于栈顶的元素的,则其一定会被放到栈顶一个合适的位置。
这样对下面的值不会有影响吗? 不会,因为后面的值来说,若是他大于 则,他的终点就是 ,否则,就可以复用的结果
利用单调栈处理后的时间复杂度就是 的时间
代码
class Solution {
public:
int sumSubarrayMins(vector<int>& arr) {
stack<int> st;
int n = arr.size();
vector<int> L(n + 2), R(n + 2);
for (int i = 1; i <= n; i ++) {
const int& v = arr[i-1];
while (st.size() && arr[st.top()] >= v)
st.pop();
if (st.size()) L[i] = st.top();
else L[i] = -1;
st.push(i-1);
}
while(st.size()) st.pop();
for (int i = n; i >= 1; i --) {
const int& v = arr[i-1];
while (st.size() && arr[st.top()] > v)
st.pop();
if (st.size()) R[i] = st.top();
else R[i] = n;
st.push(i-1);
}
long long ans = 0;
const int MOD = 1e9 + 7;
for (int i = 1; i <= n; i ++) {
ans += 1ll * (i - L[i] - 1) * (R[i] - i + 1) * arr[i-1];
ans %= MOD;
}
return ans;
}
};