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 <= A.length <= 30000
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.