Given a sequence of n integers a1, a2, ..., an, a 132 pattern is a subsequence ai, aj, ak such that i < j < k and ai < ak < aj. Design an algorithm that takes a list of n numbers as input and checks whether there is a 132 pattern in the list.
Note: n will be less than 15,000.
Example 1:
Input: [1, 2, 3, 4] Output: False Explanation: There is no 132 pattern in the sequence.
Example 2:
Input: [3, 1, 4, 2] Output: True Explanation: There is a 132 pattern in the sequence: [1, 4, 2].
Example 3:
Input: [-1, 3, 2, 0] Output: True Explanation: There are three 132 patterns in the sequence: [-1, 3, 2], [-1, 3, 0] and [-1, 2, 0].
這道題給我們了一個數組,讓我們找到 132 的模式,就是第一個數小於第二第三個數,且第三個數小於第二個數。當然最直接最暴力的方法,就是遍歷所有的三個數字的組合,然后驗證是否滿足這個規律。得莫,OJ 說打妹。那么就只能想辦法去優化了,由於暴力搜索的時間復雜度是三次方,在之前的 3Sum 那道題中,我們也有把立方的復雜度減少到平方的復雜度,相當於降了一維(降維打擊么?),其實就是先固定一個數字,然后去遍歷另外兩個數字。我們先確定哪個數字呢,當然是最小的那個啦,我們維護一個變量 mn,初始化為整型最大值,然后在遍歷數字的時候,每次用當前數字來更新 mn,然后我們判斷,若 mn 為當前數字就跳過,因為需要找到數字j的位置,數字j是大於數字i的,mn 表示的就是數字i。這樣數字i和數字j都確定了之后,就要來遍歷數字k了,范圍是從數組的最后一個位置到數字j之間,只要中間的任何一個數字滿足題目要求的關系,就直接返回 true 即可,參見代碼如下:
解法一:
class Solution { public: bool find132pattern(vector<int>& nums) { int n = nums.size(), mn = INT_MAX; for (int j = 0; j < n; ++j) { mn = min(mn, nums[j]); if (mn == nums[j]) continue; for (int k = n - 1; k > j; --k) { if (mn < nums[k] && nums[j] > nums[k]) return true; } } return false; } };
那么我們就按順序來找這三個數,首先我們來找第一個數,這個數需要最小,那么我們如果發現當前數字大於等於后面一個數字,我們就往下繼續遍歷,直到當前數字小於下一個數字停止。然后我們找第二個數字,這個數字需要最大,那么如果我們發現當前數字小於等於下一個數字就繼續遍歷,直到當前數字大雨下一個數字停止。最后就找第三個數字,我們驗證這個數字是否在之前兩個數字的中間,如果沒有找到,我們就從第二個數字的后面一個位置繼續開始重新找這三個數字,參見代碼如下:
class Solution { public: bool find132pattern(vector<int>& nums) {int n = nums.size(), i = 0, j = 0, k = 0; while (i < n) { while (i < n - 1 && nums[i] >= nums[i + 1]) ++i; j = i + 1; while (j < n - 1 && nums[j] <= nums[j + 1]) ++j; k = j + 1; while (k < n) { if (nums[k] > nums[i] && nums[k] < nums[j]) return true; ++k; } i = j + 1; } return false; } };
下面這種方法利用單調棧來做,既簡潔又高效,關於單調棧可以參見博主之前的一篇文章 LeetCode Monotonous Stack Summary 單調棧小結。思路是我們維護一個棧和一個變量 third,其中 third 就是第三個數字,也是 pattern 132 中的2,初始化為整型最小值,棧里面按順序放所有大於 third 的數字,也是 pattern 132 中的3,那么我們在遍歷的時候,如果當前數字小於 third,即 pattern 132 中的1找到了,我們直接返回 true 即可,因為已經找到了,注意我們應該從后往前遍歷數組。如果當前數字大於棧頂元素,那么我們將棧頂數字取出,賦值給 third,然后將該數字壓入棧,這樣保證了棧里的元素仍然都是大於 third 的,我們想要的順序依舊存在,進一步來說,棧里存放的都是可以維持坐標 second > third 的 second 值,其中的任何一個值都是大於當前的 third 值,如果有更大的值進來,那就等於形成了一個更優的 second > third 的這樣一個組合,並且這時彈出的 third 值比以前的 third 值更大,為什么要保證 third 值更大,因為這樣才可以更容易的滿足當前的值 first 比 third 值小這個條件,舉個例子來說吧,比如 [2, 4, 2, 3, 5],由於是從后往前遍歷,所以后三個數都不會進入 while 循環,那么棧中的數字為 5, 3, 2(其中2為棧頂元素),此時 third 還是整型最小,那么當遍歷到4的時候,終於4大於棧頂元素2了,那么 third 賦值為2,且2出棧。此時繼續 while 循環,因為4還是大於新棧頂元素3,此時 third 賦值為3,且3出棧。現在棧頂元素是5,那么 while 循環結束,將4壓入棧。下一個數字2,小於 third,則找到符合要求的序列 [2, 4, 3],參見代碼如下:
解法三:
class Solution { public: bool find132pattern(vector<int>& nums) { int third = INT_MIN; stack<int> st; for (int i = nums.size() - 1; i >= 0; --i) { if (nums[i] < third) return true; while (!st.empty() && nums[i] > st.top()) { third = st.top(); st.pop(); } st.push(nums[i]); } return false; } };
討論:這道題的一個很好的 Follow up 就是求出所有 132 模式的數組,那么解法二和解法三這種想要快速來驗證是否存在 132 模式的方法就不太適合,而解法一就比較適合了,我們是需要在找到的時候不直接 return,而是將 mn,數字j,和數字k,放到一個數組里,然后加入結果 res 中,試了題目中的例子3,是可以正確的返回那三個結果的,別的也沒怎么試過,感覺應該是正確的。
參考資料:
https://leetcode.com/problems/132-pattern/
https://leetcode.com/problems/132-pattern/discuss/94135/c_ac