1. 程式人生 > >[LeetCode] Number of Subarrays with Bounded Maximum 有界限最大值的子陣列數量

[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;
    }
};

參考資料: