【Leetcode】456. 132 Pattern

題目地址:

https://leetcode.com/problems/132-pattern/

給定一個數組AA,問這個數組中是否存在三個數A[i],A[j],A[k]A[i],A[j],A[k]滿足i<j<ki<j<k並且A[i]<A[k]<A[j]A[i]<A[k]<A[j]

法1:模擬出棧。我們可以將數組從大到小排列,然後按照這個順序進棧,看看能不能模擬出某個出棧順序剛好等於原數組。如果能模擬出來,說明132132不存在,否則說明存在。代碼如下:

import java.util.*;

class Solution {
    public boolean find132pattern(int[] nums) {
    	// 如果數組空或者長度不足3,顯然沒有
        if (nums == null || nums.length < 3) {
            return false;
        }
    
    	// 接下來把數組中數字存到一個list裏然後從大到小排序
        List<Integer> list = new ArrayList<>();
        for (int num : nums) {
            list.add(num);
        }
        list.sort((x, y) -> -Integer.compare(x, y));
       
       	// 接着開始模擬出棧順序
        Deque<Integer> stack = new ArrayDeque<>();
        int idx = 0;
        for (int i = 0; i < list.size(); i++) {
            stack.push(list.get(i));
            // 如果發現棧頂數字與數組中數字匹配,就將其出棧,並把下標移動一位
            while (!stack.isEmpty() && stack.peek() == nums[idx]) {
                stack.pop();
                idx++;
            }
        }
        
        // 如果數組空了,說明原序列可以構成出棧序列,即沒有132模式,返回false,否則返回true
        return !stack.isEmpty();
    }
}

時間複雜度O(nlogn)O(n\log n),空間O(n)O(n)

算法正確性證明:
先證明一個結論,對於入棧序列1,2,...,n1,2,...,n,那麼其出棧的序列x1,x2,...,xnx_1,x_2,...,x_n(意思是將1,2,...,n1,2,...,n按順序進棧,進棧中途可以讓數字出棧,並且出棧順序是x1,x2,...,xnx_1,x_2,...,x_n),一定不會存在i<j<ki<j<k但是xi>xk>xjx_i>x_k>x_j。並且這個結論的反面也是成立的,如果某個序列x1,x2,...,xnx_1,x_2,...,x_n,滿足對任意i<j<ki<j<kxi>xk>xjx_i>x_k>x_j一定不成立,那麼這個序列一定是某個出棧的順序。

舉個例子來說,就是如果入棧順序是1,2,31,2,3,那麼這三個數所有的排列中,唯一不可能是出棧序列的是312312。原因是,如果要出現這個序列的話,必須得33先出棧並且出棧後棧裏仍然還有兩個數字,也就是說在進棧的時候,必須中途不能出棧,那麼進棧完畢後棧是[1,2,3[1,2,3(左邊是棧底,右邊棧頂)。此時33先出棧,接下來的出棧順序一定是22先出來,所以312312的順序出棧是不可能的。通過枚舉可以驗證別的排列都可能成爲出棧的順序。

接下來證明上面的結論。
首先證明必要性,與上面的證明類似,若某個出棧順序是x1,x2,...,xnx_1,x_2,...,x_n,則一定不會存在i<j<ki<j<k但是xi>xk>xjx_i>x_k>x_j
其次證明充分性,如果1,2,...,n1,2,...,n的某個排列x1,x2,...,xnx_1,x_2,...,x_n,對任意i<j<ki<j<kxi>xk>xjx_i>x_k>x_j一定不成立,我們來證明它可以構成出棧序列。用數學歸納法。對於n=3n=3的情況,可以通過暴力枚舉證明。假設對n=kn=k結論也成立,當n=k+1n=k+1時,入棧序列是1,2,...,k+11,2,...,k+1,我們構造出棧序列(x1,x2,...,xn)(x_1,x_2,...,x_n)。由於x1x_1最先出棧,所以小於x1x_1的數字必須先全部入棧,棧爲[1,2,...,x11,x1[1,2,...,x_1-1,x_1,然後x1x_1率先出棧構成出棧序列的第一個數。接下來在序列裏去尋找x11x_1-1這個數,設整個序列是(x1,x2,x3,...,xu1,x11,xu+1,...,x12,...,x13,...,3,...,2,...1)(x_1,x_2,x_3,...,x_{u-1},x_1-1,x_{u+1},...,x_1-2,...,x_1-3,...,3,...,2,...1),由於不允許出現"312312“這個模式,所以從x2x_2xu1x_{u-1},這些數都是大於x1x_1的(否則x1,xl,x11x_1,x_l,x_1-1就是一個”312312“模式,其中l{2,3,...,u1}l\in\{2,3,...,u-1\}),也就是在x1x_1之後入棧。然而我們還可以證明{x2,x3,...,xu1}={x1+1,...,x1+u2}\{x_2,x_3,...,x_{u-1}\}=\{x_1+1,...,x_1+u-2\}(此處的意思是作爲集合相等),如若不然,就會存在xvx1+u1,v{2,3,...,u1}x_v\ge x_1+u-1,v\in \{2,3,...,u-1\}xv,x11x_v,x_1-1再接上{x1+1,...,x1+u2}{x2,x3,...,xu1}\{x_1+1,...,x_1+u-2\}-\{x_2,x_3,...,x_{u-1}\}裏面的任意一個數就會產生”312312"模式,與假設矛盾。如此一來就有{x2,x3,...,xu1}={x1+1,...,x1+u2}\{x_2,x_3,...,x_{u-1}\}=\{x_1+1,...,x_1+u-2\},接下來用歸納假設,這個序列是可以構造爲出棧序列的,直到把x11x_1-1彈出後,繼續用類似的證明方法,就可以證明整個序列是可構造的。由數學歸納法知結論正確。

要說明算法的正確性只需要將上面證明結論裏的入棧順序顛倒過來即可。

法2:單調棧。考慮數組A=(x0,x1,...,xn)A=(x_0,x_1,...,x_n),那麼對於兩個數xix_ixjx_j並且i<ji<j來講,如果有xi>xjx_i>x_j,我們需要儘可能的在(x0,...,xi1)(x_0,...,x_{i-1})中找最小值來構成132132模式。所以我們想到用一個數組BB使得B[i]B[i]存放min{x0,...,xi}\min\{x_0,...,x_i\},然後從後向前check有沒有132132模式。同時用一個單調非嚴格(嚴格也可以做,代碼裏多一步判斷即可,但複雜度不會有變化)遞減的棧存儲“22”的候選者,然後從後向前遍歷數組來枚舉“33”。

具體思路是,從後向前遍歷數組,然後將A[i]A[i]B[i]B[i]作比較。如果A[i]B[i]A[i]\le B[i],那麼A[i]A[i]不可能成爲“33”,也不可能成爲“22”,原因是A[i]A[i]之前的數都小於等於A[i]A[i],所以直接略過;如果A[i]>B[i]A[i]> B[i],那麼此時就要考察棧裏有沒有作爲“22”的數字,所以先把小於等於B[i]B[i]的全pop掉,如果能找到小於A[i]A[i]的數,那就找到了132132模式,返回true;如果棧空或者棧頂數已經大於等於A[i]A[i],那麼由於棧單調,棧裏都不可能存在“22”,所以就將A[i]A[i]進棧。如此遍歷數組直到找到答案,或找不到則最後返回true。

現在給出算法的簡略證明。算法的本質是在枚舉,我們只需要證明棧裏面存儲的都是“22”的候選者即可。現在從數組後向前開始遍歷。首先,如果發生A[i]B[i]A[i]\le B[i],由於A[i]A[i]比其前面的數字都要小於等於,所以A[i]A[i]不可能成爲“33”,也不可能成爲“22”,所以可以略過。如果發現了第一個A[i]>B[i]A[i]>B[i],此時棧裏爲空,所以A[i]A[i]不能作爲“33”出現,但是仍然有可能作爲“22”出現,所以先入棧。接着如果發現了第二個A[i]>B[i]A[i]>B[i],此時如果棧頂數小於等於B[i]B[i],那麼這個數就會小於所有B[i]B[i]以及其之前的數,所以它就永遠不會作爲“22”出現,所以可以出棧;如果其正好在A[i]A[i]B[i]B[i]之間,那就找到了;如果其大於等於A[i]A[i],那麼說明A[i]A[i]不可能作爲“33”出現了,因爲其後面能作爲"22"出現的唯一候選者不滿足條件,然而A[i]A[i]還是有可能作爲“22”出現,所以將其進棧。我們可以看出,對於每一種情況,棧裏始終存放的都是能作爲“22”的候選者,而且棧恰好是從棧底到棧頂是(非嚴格)遞減排序的。所以說,如果數組中存在“132132”模式的話,當遍歷到第一個可以作爲“33”的數字時,棧裏的數字已經排好序了,只需驗出那個作爲“22”的數即可。也就是說,如果數組中存在“132132”模式的話,算法一定找到並且會返回true。所以算法正確。

代碼如下:

import java.util.ArrayDeque;
import java.util.Deque;

public class Solution {
    public boolean find132pattern(int[] nums) {
    	// 判斷顯然不存在的情形
        if (nums == null || nums.length < 3) {
            return false;
        }
        
        Deque<Integer> stack = new ArrayDeque<>();
        // 開始算前綴min
        int[] min = new int[nums.length];
        min[0] = nums[0];
        for (int i = 1; i < nums.length; i++) {
            min[i] = Math.min(min[i - 1], nums[i]);
        }
        
        // 從後向前遍歷數組
        for (int j = nums.length - 1; j >= 0; j--) {
            if (nums[j] > min[j]) {
                while (!stack.isEmpty() && stack.peek() <= min[j]) {
                    stack.pop();
                }
                if (!stack.isEmpty() && stack.peek() < nums[j]) {
                    return true;
                }
                stack.push(nums[j]);
            }
        }
        
        return false;
    }
}

時空複雜度O(n)O(n)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章