907. 子數組的最小值之和

中等 單調棧 動態規劃

思路

枚舉所有子數組太慢了,把思路逆轉過來,去找最小值固定為某數的子數組有多少個。
比如 arr=[1,2,3,3,5,2,6,1]arr=[1,2,3,3,5,2,6,1] ,最小值為 33 的子數組有

[3],[3],[3,3],[3,5],[3,3,5][3],[3],[3,3],[3,5],[3,3,5]

共五個,那麼對於答案的貢獻就是 3×5=153\times{5}=15


就需要由 3 往左右兩邊擴張,找到以下兩個位置:

  • 當前位置「左側小於自己,且距離最近數字」的位置left
  • 當前位置「右側小於自己,且距離最近數字」的位置right
    這一步就用單調棧去做。 對於 232\to3
  • left是在彈出的時的棧頂元素 121\to2
  • right是將自己彈出的元素 525\to2
    左邊可以選擇的起點有 21=12-1=1 個(包含 3 本身)
    右邊可以選擇的結尾有 52=35-2=3 個(包含 3 本身,分別是3, 3, 5)
    兩邊相乘,一共 3 個,對應到 [3],[3,3],[3,3,5][3],[3,3],[3,3,5] 這三個子陣列。

圖片

對於 333\to3

  • left是彈出時的棧頂元素 232\to3
  • right是將自己彈出的元素 525\to2 。 左邊可以選擇的個數是 32=13-2=1 個,只有 3 可選 右邊可以選擇的結尾一共有 53=25-3=2 個,分別是 3, 5。 兩邊相乘,一共 2 個,對應到 [3],[3,5][3],[3,5] 這兩個子陣列。

程式碼

1. 單調棧

class Solution {
private:
    const int MOD = 1e9 + 7;
    static const int MX = 30001;
    array<int, MX> stk; // 擺放的是位置
    array<array<int, 2>, MX> pos; // 紀錄左右距離自己最近的最小值的位置
public:
    int sumSubarrayMins(vector<int>& arr) {
        int n = arr.size();
        int j = -1; // 閉區間
        for(int i = 0; i < n; i++) {
            while(j >= 0 && arr[stk[j]] > arr[i]) {
                pos[stk[j]][1] = i; // 右邊比自己小的是現在的位置
                pos[stk[j]][0] = j - 1 >= 0 ? stk[j - 1] : -1; // 左邊是新的棧頂元素
                --j;
            }
            stk[++j] = i;
        }
        while(j >= 0) {
            pos[stk[j]][1] = n; // 右邊是 n, 代表沒有比較小的, 後續計算用
            pos[stk[j]][0] = j - 1 >= 0 ? stk[j - 1] : -1;
            --j;
        }
        // 不需要調整右側,因為相同數值會被壓入棧,結算時獨立運算
        long long res = 0;
        for(int i = 0; i < n; i++) { // 算答案的部分可以跟上面的迴圈合併
            long long add = (long long)arr[i] * (i - pos[i][0]) * (pos[i][1] - i);
            // printf("left = %d, right = %d, ", pos[i][0], pos[i][1]);
            // printf("add: %d * %d * %d = %d\n", arr[i], i - pos[i][0], pos[i][1] - i, add);
            res += add; // 開區間
            res %= MOD;
        }
        return res;
    }
};

2. 單調棧 合併結算

class Solution {
private:
    const int MOD = 1e9 + 7;
    static const int MX = 30001;
    array<int, MX> stk; // 擺放的是位置
    array<array<int, 2>, MX> pos; // 紀錄左右距離自己最近的最小值的位置
public:
    int sumSubarrayMins(vector<int>& arr) {
        int n = arr.size();
        int j = -1; // 閉區間
        long long res = 0;
        for(int i = 0; i < n; i++) {
            while(j >= 0 && arr[stk[j]] > arr[i]) {
                int right = i; // 右邊比自己小的是現在的位置
                int left = j - 1 >= 0 ? stk[j - 1] : -1; // 左邊是新的棧頂元素
                // 結算的是當前的棧頂元素
                res += (long long)arr[stk[j]] * (stk[j] - left) * (right - stk[j]);
                res %= MOD;
                --j;
            }
            stk[++j] = i;
        }
        while(j >= 0) {
            int right = n; // 右邊是 n, 代表沒有比較小的, 後續計算用
            int left = j - 1 >= 0 ? stk[j - 1] : -1;
            res += (long long)arr[stk[j]] * (stk[j] - left) * (right - stk[j]);
            res %= MOD;
            --j;
        }
        return res;
    }
};

複雜度分析

  • 時間複雜度:O(n)O(n)
  • 空間複雜度:O(n)O(n)

顯示設定

背景線條
顯示背景網格線條
懸停發光
滑鼠懸停時顯示霓虹效果
聚光燈
跟隨滑鼠的聚光燈效果
背景透明度
開啟透明玻璃效果
主題顏色
自訂主要顏色