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; } };
參考資料:
https://leetcode.com/problems/number-of-subarrays-with-bounded-maximum/solution/
https://leetcode.com/problems/number-of-subarrays-with-bounded-maximum/discuss/117585/Java-9-liner