Let's call any (contiguous) subarray B (of A) a mountain if the following properties hold:
B.length >= 3
- There exists some
0 < i < B.length - 1
such thatB[0] < B[1] < ... B[i-1] < B[i] > B[i+1] > ... > B[B.length - 1]
(Note that B could be any subarray of A, including the entire array A.)
Given an array A
of integers, return the length of the longest mountain.
Return 0
if there is no mountain.
Example 1:
Input: [2,1,4,7,3,2,5] Output: 5 Explanation: The largest mountain is [1,4,7,3,2] which has length 5.
Example 2:
Input: [2,2,2] Output: 0 Explanation: There is no mountain.
Note:
0 <= A.length <= 10000
0 <= A[i] <= 10000
Follow up:
- Can you solve it using only one pass?
- Can you solve it in
O(1)
space?
這道題給了我們一個數組,然后定義了一種像山一樣的子數組,就是先遞增再遞減的子數組,注意這里是強行遞增或者遞減的,並不存在相等的情況。那么實際上這道題就是讓在數組中尋找一個位置,使得以此位置為終點的遞增數組和以此位置為起點的遞減數組的長度最大。而以某個位置為起點的遞減數組,如果反個方向來看,其實就是就該位置為終點的遞增數列,那么既然都是求最長的遞增數列,我們可以分別用兩個 dp 數組 up 和 down,其中 up[i] 表示以 i 位置為終點的最長遞增數列的個數,down[i] 表示以 i 位置為起點的最長遞減數列的個數,這樣我們正向更新 up 數組,反向更新 down 數組即可。先反向更新好了 down 之后,在正向更新 up 數組的同時,也可以更新結果 res,當某個位置的 up[i] 和 down[i] 均大於0的時候,那么就可以用 up[i] + down[i] + 1 來更新結果 res 了,參見代碼如下:
解法一:
class Solution { public: int longestMountain(vector<int>& A) { int res = 0, n = A.size(); vector<int> up(n), down(n); for (int i = n - 2; i >= 0; --i) { if (A[i] > A[i + 1]) down[i] = down[i + 1] + 1; } for (int i = 1; i < n; ++i) { if (A[i] > A[i - 1]) up[i] = up[i - 1] + 1; if (up[i] > 0 && down[i] > 0) res = max(res, up[i] + down[i] + 1); } return res; } };
我們可以對空間進行優化,不必使用兩個數組來記錄所有位置的信息,而是只用兩個變量 up 和 down 來分別記錄以當前位置為終點的最長遞增數列的長度,和以當前位置為終點的最長遞減數列的長度。 我們從 i=1 的位置開始遍歷,因為山必須要有上坡和下坡,所以 i=0 的位置永遠不可能成為 peak。此時再看,如果當前位置跟前面的位置相等了,那么當前位置的 up 和 down 都要重置為0,從當前位置開始找新的山,和之前的應該斷開。或者是當 down 不為0,說明此時是在下坡,如果當前位置大於之前的了,突然變上坡了,那么之前的累計也需要重置為0。然后當前位置再進行判斷,若大於前一個位置,則是上坡,up 自增1,若小於前一個位置,是下坡,down 自增1。當 up 和 down 同時為正數,則用 up+down+1 來更新結果 res 即可,參見代碼如下:
解法二:
class Solution { public: int longestMountain(vector<int>& A) { int res = 0, up = 0, down = 0, n = A.size(); for (int i = 1; i < n; ++i) { if ((down && A[i - 1] < A[i]) || (A[i - 1] == A[i])) { up = down = 0; } if (A[i - 1] < A[i]) ++up; if (A[i - 1] > A[i]) ++down; if (up > 0 && down > 0) res = max(res, up + down + 1); } return res; } };
我們可以換一種思路,還是一次遍歷就行,進行 while 循環,條件是 i < n-1,然后判斷,當前數字大於等於下一個數字則跳過,因為我們希望首先上坡,當找到遞增的起點i后,則再開始循環,找山頂 peak,找到了之后,再進行下坡,找到山腳j,這樣如果i,peak,和j都不相同的話,說明找到了一個完整的山,用 j-i+1 來更新結果 res 即可,然后i從j開始繼續遍歷,參見代碼如下:
解法三:
class Solution { public: int longestMountain(vector<int>& A) { int res = 0, i = 0, n = A.size(); while (i < n - 1) { while (i < n - 1 && A[i] >= A[i + 1]) ++i; int peak = i; while (peak < n - 1 && A[peak] < A[peak + 1]) ++peak; int j = peak; while (j < n - 1 && A[j] > A[j + 1]) ++j; if (i < peak && peak < j) res = max(res, j - i + 1); i = j; } return res; } };
也可以再換種思路,首先來找山峰,peak 的范圍是 [1, n-1],因為首尾兩個數字都不能做山峰,能做山峰的位置上的數必須大於其左右兩邊的數字,然后分別向左右兩個方向遍歷,這樣就可以找到完整的山,用 right-left+1 來更新結果 res,參見代碼如下:
解法四:
class Solution { public: int longestMountain(vector<int>& A) { int res = 0, n = A.size(); for (int i = 1; i < n - 1; ++i) { if (A[i - 1] < A[i] && A[i + 1] < A[i]) { int left = i - 1, right = i + 1; while (left > 0 && A[left - 1] < A[left]) --left; while (right < n - 1 && A[right] > A[right + 1]) ++right; res = max(res, right - left + 1); } } return res; } };
Github 同步地址:
https://github.com/grandyang/leetcode/issues/845
參考資料:
https://leetcode.com/problems/longest-mountain-in-array/