20221028 - 907. Sum of Subarray Minimums 子数组的最小值之和(单调栈)

105 阅读1分钟

Given an array of integers arr, find the sum of min(b), where b ranges over every (contiguous) subarray of arr. Since the answer may be large, return the answer modulo 109+710^{9}+7.

Example 1

Input: arr = [3,1,2,4]
Output: 17
Explanation: 
Subarrays are [3], [1], [2], [4], [3,1], [1,2], [2,4], [3,1,2], [1,2,4], [3,1,2,4]. 
Minimums are 3, 1, 2, 4, 1, 1, 2, 1, 1, 1.
Sum is 17.

Example 2

Input: arr = [11,81,94,43,3]
Output: 444

Constraints

  • 11 <= arr.length <= 31043 * 10^4
  • 11 <= arr[i] <= 31043 * 10^4

Solution

二重循环遍历,滑动窗口,不出所料超时。

class Solution {
public:
    int sumSubarrayMins(vector<int>& arr) {
        int n = arr.size();
        int left, right;
        int sum = 0;
        for (left = 0; left < n; left++) {
            int min = arr[left];
            for (right = left; right < n; right++) {
                if (arr[right] < min) min = arr[right];
                sum += min % ((int)1e9 + (int)7);
                sum %= (int)1e9 + (int)7;
            }
        }
        return sum;
    }
};

为何超时?题目中最大为 31043*{10}^4n2n^2 已接近 109{10}^9 且有 MOD 运算,因此必然会超时。

题解:单调栈

class Solution {
public:
    int sumSubarrayMins(vector<int>& arr) {
        int n = arr.size();
        vector<int> monoStack;
        vector<int> left(n), right(n);
        for (int i = 0; i < n; i++) {
            while (!monoStack.empty() && arr[monoStack.back()] > arr[i]) {
                monoStack.pop_back();
            }
            if (monoStack.empty()) {
                left[i] = i + 1;
            } else {
                left[i] = i - monoStack.back();
            }
            monoStack.push_back(i);
        }
        monoStack.clear();
        for (int i = n - 1; i >= 0; i--) {
            while (!monoStack.empty() && arr[monoStack.back()] >= arr[i]) {
                monoStack.pop_back();
            }
            if (monoStack.empty()) {
                right[i] = n - i;
            } else {
                right[i] = monoStack.back() - i;
            }
            monoStack.push_back(i);
        }
        long long ans = 0;
        for (int i = 0; i < n; i++) {
            ans = (ans + (long long)arr[i] * left[i] * right[i]) % 1000000007;
        }
        return ans;
    }
};

注意判断左右边界的循环条件不能同时闭区间,不然左右会重复算。

题目链接:907. 子数组的最小值之和 - 力扣(LeetCode)