[LeetCode] 907. Sum of Subarray Minimums 子數組最小值之和



Given an array of integers A, find the sum of min(B), where B ranges over every (contiguous) subarray of A.html

Since the answer may be large, return the answer modulo 10^9 + 7.git

Example 1:github

Input: [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.

Note:數組

  1. 1 <= A.length <= 30000
  2. 1 <= A[i] <= 30000



這道題給了一個數組,對於全部的子數組,找到最小值,並返回累加結果,並對一個超大數取餘。因爲咱們只關心子數組中的最小值,因此對於數組中的任意一個數字,須要知道其是多少個子數組的最小值。就拿題目中的例子 [3,1,2,4] 來分析,開始遍歷到3的時候,其自己就是一個子數組,最小值也是其自己,累加到結果 res 中,此時 res=3,而後看下個數1,是小於3的,此時新產生了兩個子數組 [1] 和 [3,1],且最小值都是1,此時在結果中就累加了 2,此時 res=5。接下來的數字是2,大於以前的1,此時會新產生三個子數組,其自己單獨會產生一個子數組 [2],能夠先把這個2累加到結果 res 中,而後就是 [1,2] 和 [3,1,2],能夠發現新產生的這兩個子數組的最小值仍是1,跟以前計算數字1的時候同樣,能夠直接將以1結尾的子數組最小值之和加起來,那麼以2結尾的子數組最小值之和就是 2+2=4,此時 res=9。對於最後一個數字4,其單獨產生一個子數組 [4],還會再產生三個子數組 [3,1,2,4], [1,2,4], [2,4],其並不會對子數組的最小值產生影響,因此直接加上以2結尾的子數組最小值之和,總共就是 4+4=8,最終 res=17。優化

分析到這裏,就知道咱們其實關心的是以某個數字結尾時的子數組最小值之和,能夠用一個一維數組 dp,其中 dp[i] 表示以數字 A[i] 結尾的全部子數組最小值之和,將 dp[0] 初始化爲 A[0],結果 res 也初始化爲 A[0]。而後從第二個數字開始遍歷,若大於等於前一個數字,則當前 dp[i] 賦值爲 dp[i-1]+A[i],前面的分析已經解釋了,當前數字 A[i] 組成了新的子數組,同時因爲 A[i] 不會影響最小值,因此要把以前的最小值之和再加一遍。假如小於前一個數字,就須要向前遍歷,去找到第一個小於 A[i] 的位置j,假如j小於0,表示前面全部的數字都是小於 A[i] 的,那麼 A[i] 是前面 i+1 個以 A[i] 結尾的子數組的最小值,累加和爲 (i+1) x A[i],若j大於等於0,則須要分紅兩部分累加,dp[j] + (i-j)xA[i],這個也不難理解,前面有 i-j 個以 A[i] 爲結尾的子數組的最小值是 A[i],而再前面的子數組的最小值就不是 A[i] 了,可是仍是須要加上一遍其自己的最小值之和,由於每一個子數組末尾都加上 A[i] 都可以組成一個新的子數組,最終的結果 res 就是將 dp 數組累加起來便可,別忘了對超大數取餘,參見代碼以下:code



解法一:htm

class Solution {
public:
    int sumSubarrayMins(vector<int>& A) {
        int res = A[0], n = A.size(), M = 1e9 + 7;
        vector<int> dp(n);
        dp[0] = A[0];
        for (int i = 1; i < n; ++i) {
            if (A[i] >= A[i - 1]) dp[i] = dp[i - 1] + A[i];
            else {
                int j = i - 1;
                while (j >= 0 && A[i] < A[j]) --j;
                dp[i] = (j < 0) ? (i + 1) * A[i] : (dp[j] + (i - j) * A[i]);
            }
            res = (res + dp[i]) % M;
        }
        return res;
    }
};



上面的方法雖然 work,但不是很高效,緣由是在向前找第一個小於當前的數字,每次都要線性遍歷一遍,形成了平方級的時間複雜度。而找每一個數字的前小數字或是後小數字,正是單調棧擅長的,能夠參考博主以前的總結貼 LeetCode Monotonous Stack Summary 單調棧小結。這裏咱們用一個單調棧來保存以前一個小的數字的位置,棧裏先提早放一個 -1,做用會在以後講解。仍是須要一個 dp 數組,跟上面的定義基本同樣,可是爲了不數組越界,將長度初始化爲 n+1,其中 dp[i] 表示以數字 A[i-1] 結尾的全部子數組最小值之和。對數組進行遍歷,當棧頂元素不是 -1 且 A[i] 小於等於棧頂元素,則將棧頂元素移除。這樣棧頂元素就是前面第一個比 A[i] 小的數字,此時 dp[i+1] 更新仍是跟以前同樣,分爲兩個部分,因爲知道了前面第一個小於 A[i] 的數字位置,用當前位置減去棧頂元素位置再乘以 A[i],就是以 A[i] 爲結尾且最小值爲 A[i] 的子數組的最小值之和,而棧頂元素以前的子數組就不受 A[i] 影響了,直接將其 dp 值加上便可。將當前位置壓入棧,並將 dp[i+1] 累加到結果 res,同時對超大值取餘,參見代碼以下:blog



解法二:leetcode

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



再來看一種解法,因爲對於每一個數字,只要知道了其前面第一個小於其的數字位置,和後面第一個小於其的數字位置,就能知道當前數字是多少個子數組的最小值,直接相乘累加到結果 res 中便可。這裏咱們用兩個單調棧 st_pre 和 st_next,棧裏放一個數對兒,由數字和其在原數組的座標組成。還須要兩個一維數組 left 和 right,其中 left[i] 表示以 A[i] 爲結束爲止且 A[i] 是最小值的子數組的個數,right[i] 表示以 A[i] 爲起點且 A[i] 是最小值的子數組的個數。對數組進行遍歷,當 st_pre 不空,且棧頂元素大於 A[i],移除棧頂元素,這樣剩下的棧頂元素就是 A[i] 左邊第一個小於其的數字的位置,假如棧爲空,說明左邊的全部數字都小於 A[i],則 left[i] 賦值爲 i+1,不然賦值爲用i減去棧頂元素在原數組中的位置的值,而後將 A[i] 和i組成數對兒壓入棧 st_pre。對於 right[i] 的處理也很相似,先將其初始化爲 n-i,而後看若 st_next 不爲空且棧頂元素大於 A[i],而後取出棧頂元素t,因爲棧頂元素t是大於 A[i]的,因此 right[t.second] 就能夠更新爲 i-t.second,而後將 A[i] 和i組成數對兒壓入棧 st_next,最後再遍歷一遍原數組,將每一個 A[i] x left[i] x right[i] 算出來累加起來便可,別忘了對超大數取餘,參見代碼以下:get



解法三:

class Solution {
public:
    int sumSubarrayMins(vector<int>& A) {
        int res = 0, n = A.size(), M = 1e9 + 7;
        stack<pair<int, int>> st_pre, st_next;
        vector<int> left(n), right(n);
        for (int i = 0; i < n; ++i) {
            while (!st_pre.empty() && st_pre.top().first > A[i]) {
                st_pre.pop();
            }
            left[i] = st_pre.empty() ? (i + 1) : (i - st_pre.top().second);
            st_pre.push({A[i], i});
            right[i] = n - i;
            while (!st_next.empty() && st_next.top().first > A[i]) {
                auto t = st_next.top(); st_next.pop();
                right[t.second] = i - t.second;
            }
            st_next.push({A[i], i});
        }
        for (int i = 0; i < n; ++i) {
            res = (res + A[i] * left[i] * right[i]) % M;
        }
        return res;
    }
};



咱們也能夠對上面的解法進行空間上的優化,只用一個單調棧,用來記錄當前數字以前的第一個小的數字的位置,而後遍歷每一個數字,可是要多遍歷一個數字,i從0遍歷到n,當 i=n 時,cur 賦值爲0,不然賦值爲 A[i]。而後判斷若棧不爲空,且 cur 小於棧頂元素,則取出棧頂元素位置 idx,因爲是單調棧,那麼新的棧頂元素就是 A[idx] 前面第一個較小數的位置,因爲此時棧可能爲空,因此再去以前要判斷一下,若爲空,則返回 -1,不然返回棧頂元素,用 idx 減去棧頂元素就是以 A[idx] 爲結尾且最小值爲 A[idx] 的子數組的個數,而後用i減去 idx 就是以 A[idx] 爲起始且最小值爲 A[idx] 的子數組的個數,而後 A[idx] x left x right 就是 A[idx] 這個數字當子數組的最小值之和,累加到結果 res 中並對超大數取餘便可,參見代碼以下:



解法四:

class Solution {
public:
    int sumSubarrayMins(vector<int>& A) {
        int res = 0, n = A.size(), M = 1e9 + 7;
        stack<int> st;
        for (int i = 0; i <= n; ++i) {
            int cur = (i == n) ? 0 : A[i];
            while (!st.empty() && cur < A[st.top()]) {
                int idx = st.top(); st.pop();
                int left = idx - (st.empty() ? -1 : st.top());
                int right = i - idx;
                res = (res + A[idx] * left * right) % M;
            }
            st.push(i);
        }
        return res;
    }
};



Github 同步地址:

https://github.com/grandyang/leetcode/issues/907



參考資料:

https://leetcode.com/problems/sum-of-subarray-minimums/

https://leetcode.com/problems/sum-of-subarray-minimums/discuss/170857/One-stack-solution

https://leetcode.com/problems/sum-of-subarray-minimums/discuss/222895/Java-No-Stack-solution.

https://leetcode.com/problems/sum-of-subarray-minimums/discuss/170769/Java-O(n)-monotone-stack-with-DP

https://leetcode.com/problems/sum-of-subarray-minimums/discuss/178876/stack-solution-with-very-detailed-explanation-step-by-step



LeetCode All in One 題目講解彙總(持續更新中...)

相關文章
相關標籤/搜索