[LeetCode] Number of Subarrays with Bounded Maximum 有界限最大值的子陣列數量
We are given an array A
of positive integers, and two positive integers L
and R
(L <= R
).
Return the number of (contiguous, non-empty) subarrays such that the value of the maximum array element in that subarray is at least L
and at most R
.
Example : Input: A = [2, 1, 4, 3] L = 2 R = 3 Output: 3 Explanation: There are three subarrays that meet the requirements: [2], [2, 1], [3].
Note:
- L, R and
A[i]
will be an integer in the range[0, 10^9]
. - The length of
A
will be in the range of[1, 50000]
.
這道題給了我們一個數組,又給了我們兩個數字L和R,表示一個區間範圍,讓我們求有多少個這樣的子陣列,使得其最大值在[L, R]區間的範圍內。既然是求子陣列的問題,那麼最直接,最暴力的方法就是遍歷所有的子陣列,然後維護一個當前的最大值,只要這個最大值在[L, R]區間的範圍內,結果res自增1即可。但是這種最原始,最粗獷的暴力搜尋法,OJ不答應。但是其實我們略作優化,就可以通過了。優化的方法是,首先,如果當前數字大於R了,那麼其實後面就不用再遍歷了,不管當前這個數字是不是最大值,它都已經大於R了,那麼最大值可能會更大,所以沒有必要再繼續遍歷下去了。同樣的剪枝也要加在內層迴圈中加,當curMax大於R時,直接break掉內層迴圈即可,參見程式碼如下:
解法一:
class Solution { public: int numSubarrayBoundedMax(vector<int>& A, int L, int R) { int res = 0, n = A.size(); for (int i = 0; i < n; ++i) { if (A[i] > R) continue; int curMax = INT_MIN; for (int j = i; j < n; ++j) { curMax= max(curMax, A[j]); if (curMax > R) break; if (curMax >= L) ++res; } } return res; } };
雖然上面的方法做了剪枝後能通過OJ,但是我們能不能線上性的時間複雜度內完成呢。答案是肯定的,我們先來看一種官方解答貼中的方法,這種方法是用一個子函式來算出最大值在[-∞, x]範圍內的子陣列的個數,而這種區間只需要一個迴圈就夠了,為啥呢?我們來看陣列[2, 1, 4, 3]的最大值在[-∞, 4]範圍內的子陣列的個數。當遍歷到2時,只有一個子陣列[2],遍歷到1時,有三個子陣列,[2], [1], [2,1]。當遍歷到4時,有六個子陣列,[2], [1], [4], [2,1], [1,4], [2,1,4]。當遍歷到3時,有十個子陣列。其實如果長度為n的陣列的最大值在範圍[-∞, x]內的話,其所有子陣列都是符合題意的,而長度為n的陣列共有n(n+1)/2個子陣列,剛好是等差數列的求和公式。所以我們在遍歷陣列的時候,如果當前陣列小於等於x,則cur自增1,然後將cur加到結果res中;如果大於x,則cur重置為0。這樣我們可以正確求出最大值在[-∞, x]範圍內的子陣列的個數。而要求最大值在[L, R]範圍內的子陣列的個數,只需要用最大值在[-∞, R]範圍內的子陣列的個數,減去最大值在[-∞, L-1]範圍內的子陣列的個數即可,參見程式碼如下:
解法二:
class Solution { public: int numSubarrayBoundedMax(vector<int>& A, int L, int R) { return count(A, R) - count(A, L - 1); } int count(vector<int>& A, int bound) { int res = 0, cur = 0; for (int x : A) { cur = (x <= bound) ? cur + 1 : 0; res += cur; } return res; } };
下面這種解法也是線性時間複雜度的,跟上面解法的原理很類似,只不過沒有寫子函式而已。我們使用left和right來分別標記子陣列的左右邊界,使得其最大值在範圍[L,R]內。那麼當遍歷到的數字大於等於L時,right賦值為當前位置i,那麼每次res加上right - left,隨著right的不停自增1,每次加上的right - left,實際上也是一個等差數列,跟上面解法中的子函式本質時一樣的。當A[i]大於R的時候,left = i,那麼此時A[i]肯定也大於等於L,於是rihgt=i,那麼right - left為0,相當於上面的cur重置為0的操作,發現本質聯絡了吧,參見程式碼如下:
解法三:
class Solution { public: int numSubarrayBoundedMax(vector<int>& A, int L, int R) { int res = 0, left = -1, right = -1; for (int i = 0; i < A.size(); ++i) { if (A[i] > R) left = i; if (A[i] >= L) right = i; res += right - left; } return res; } };
我們可以對上面的解法稍稍做下優化,在A[i] > R的時候,left和right都賦值為i,然後continue,這樣省去了後面的用0來更新結果res的步驟,能提高一些執行效率,參見程式碼如下:
解法四:
class Solution { public: int numSubarrayBoundedMax(vector<int>& A, int L, int R) { int res = 0, left = -1, right = -1; for (int i = 0; i < A.size(); ++i) { if (A[i] > R) { left = right = i; continue; } if (A[i] >= L) right = i; res += right - left; } return res; } };
參考資料: