【LeetCode】单调栈贡献题

201 阅读1分钟

907. 子数组的最小值之和

题解:以arr[i]arr[i] 作为最小值,计算贡献的数值,需要注意数组中有重复的元素,需要去除重复计算,l[i]l[i] 严格小于 a[i]a[i] 的位置, r[i]r[i] 是小于等于的 a[i]a[i] 的位置,a[i]a[i] 的贡献是 (r[i]i)(il[i])a[i](r[i]-i)*(i-l[i])*a[i], 使用单调栈来求 llrr

class Solution {
public:
    int sumSubarrayMins(vector<int>& arr) {
        int n = arr.size();
        vector<long long> l(n, -1), r(n, n);
        stack<int> st;
        for(int i=0; i<n; i++){
            while(!st.empty() && arr[st.top()] >= arr[i]){
                r[st.top()] = i;
                st.pop();
            }
            if(!st.empty()){
                l[i] = st.top();
            }
            st.push(i);
        }
        long long res = 0;
        int mod = 1e9+7;
        for(int i=0; i<n; i++){
          res += (i - l[i]) * (r[i] - i) * arr[i] % mod;
        }
        return (res+mod)%mod;
    }
};

1856. 子数组最小乘积的最大值

单调栈 + 前缀和

class Solution {
public:
    int maxSumMinProduct(vector<int>& nums) {
        long long res = 0;
        int n = nums.size();
        // 左边是严格小于 nums[i], 右边非严格小于等于
        vector<long long> l(n, -1), r(n, n);
        stack<int> st;
        for(int i=0; i<n; i++){
            while(!st.empty() && nums[st.top()] >= nums[i]){
                r[st.top()] = i;
                st.pop();
            }
            if(!st.empty()) l[i] = st.top();
            st.push(i);
        }
        vector<long long> f(n+1);
        int mod = 1e9+7;
        for(int i=1; i<=n; i++) f[i] = f[i-1] + nums[i-1];
        for(int i=0; i<n; i++){
            long long t = nums[i]*(f[r[i]] - f[l[i]+1]);
            res = max(res, t);
        }
        return res%mod;
    }
};

2104. 子数组范围和

题解:最大差值, 以 a[i]a[i] 作为最大值的个数 k1k_1, a[i]a[i] 作为最小值的个数为 k2k_2, a[i]a[i] 贡献的数值为 (k1k2)a[i](k_1 - k_2)*a[i], 即 k1a[i]k2a[i]k_1*a[i] - k_2*a[i], 枚举所有元素作为最大值之和 - 最小值之和。 使用 单调栈来求做为最大值的个数之和或者最小值个数之和。

class Solution {
public:
    int n;
    long long getMax(vector<int>& nums){
        nums.push_back(1e9+1);
        stack<int> st;
        int m = n+1;
        long long res = 0;
        for(int i=0; i<m; i++){
            while(!st.empty() && nums[st.top()] < nums[i]){
                int idx = st.top();
                st.pop();
                int next = -1;
                if(!st.empty()){
                    next = st.top();
                }
                res += ((long long)nums[idx]) * (i - idx) *(idx-next);
            }
            st.push(i);
        }
        return res;
    }
    long long subArrayRanges(vector<int>& nums) {
        n = nums.size();
        long long maxv = getMax(nums);
        // 小技巧, 将数值反过来再求一般最大值,也就是最小值了。
        for(int i=0; i<n; i++) nums[i] = - nums[i];
        long long minv = getMax(nums); // 最小值是负数
        return maxv+minv;
    }
};

总结: 通过单调栈求左右两边的最值来计算贡献的数值,注意重复数据需要去除重复计算。