利用单调栈解决区间问题

88 阅读1分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第29天,点击查看活动详情

单调栈是一个存储拥有单调性的序列的栈式结构,一般可以使用其完成某些上升或下降的问题。 本文,就介绍一下,单调栈如何来优化时间复杂度。

子数组的最小值之和

给定一个整数数组 arr,找到 min(b) 的总和,其中 b 的范围为 arr 的每个(连续)子数组。

数据范围

  • 1 <= arr.length <= 3 * 10^4
  • 1 <= arr[i] <= 3 * 10^4

解法

其实数据范围挺小的,稍不注意,我暴力都可以过。

可以再丧心病狂一点,把数据拉到 10610^6 级别

极致的暴力做法(O(n3)O(n^3))

我们可以枚举这个序列左右端点,枚举左右端点的时间复杂度为O(n2)O(n^2)

枚举完每一个序列左右端点后,再对其区间求 minmin 操作,总体时间复杂度 O(n3)O(n^3)

所以叫做极致的暴力

暴力做法(O(n2)O(n^2))

想一想,有必要去枚举左右端点么?

换一个思路,去寻找当前这个值可以管的左边最长的和右边最长的是多少。

设当前位置是now,区间[L,now]和区间[now,R]的值都是大于等于now值的则可以得到(nowL+1)×(Rnow+1)个区域(乘法原理)则可以获得的值就是(nowL+1)×(Rnow+1)×arr[now]设当前位置是now,区间 [L, now]和区间[now, R]的值都是大于等于now值的 \\ 则可以得到 (now - L + 1) \times (R - now + 1) 个区域 (乘法原理) \\ 则可以获得的值就是 (now - L + 1) \times (R - now + 1) \times arr[now] \\

去寻找每一个区间的左右最长延伸,所耗费的时间复杂度为 O(n)O(n)

枚举 n 个值,总时间复杂度为 O(n2)O(n^2)

单调栈(O(n)O(n))

其实吧,预处理每一个区间的 [L,R][L, R] 这一件事情,我们可以做的更好!

这个地方就是单调栈的用途所在了,我们处理了n次 [L,R][L, R] 区间,试问,真的没有重复循环的地方吗?

举个例子:

1,4, 3, 2, 1

对于它来说,4,3,2,1的左区间定值都没有任何变化,就是1!

这里就是重复计算的地方。

可以想个方法来预处理这n个操作。

一个比较好的办法是维护一个单调递增的序列,若是 当前的 curcur 是小于栈顶的元素的,则其一定会被放到栈顶一个合适的位置。

这样对下面的值不会有影响吗? 不会,因为后面的值来说,若是他大于 curcur则,他的终点就是 curcur,否则,就可以复用curcur的结果

利用单调栈处理后的时间复杂度就是 O(n)O(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;
    }
};