棧和佇列:滑動視窗和陣列變樹問題

LingLee_荊棘鳥發表於2017-07-19

1.滑動視窗問題

有一個整型陣列 arr 和一個大小為 w 的視窗從陣列的最左邊滑到最右邊,視窗每次向右邊滑一個位置。 返回一個長度為n-w+1的陣列res,res[i]表示每一種視窗狀態下的最大值。 以陣列為[4,3,5,4,3,3,6,7]w=3為例。因為第一個視窗[4,3,5]的最大值為5,第二個視窗[3,5,4]的最大值為5,第三個視窗[5,4,3]的最大值為5。第四個視窗[4,3,3]的最大值為4。第五個視窗[3,3,6]的最大值為6。第六個視窗[3,6,7]的最大值為7。所以最終返回[5,5,5,4,6,7]


給定整形陣列arr及它的大小n,同時給定w,請返回res陣列。保證w小於等於n,同時保證陣列大小小於等於500。


思路:


普通方法,對於n個元素,每個視窗m次比較,時間複雜度O(nm)的原地解決方案。藉助雙端佇列來實現時間複雜度


O(n)



雙端佇列qmax存陣列中的下標,其中對頭---當前視窗下最大值的下標,在隊尾的push和pop是為了使當對頭彈出時,


能夠滿足後面的更小的值。


(1)佇列為空,直接將i入隊


(2)佇列不為空,在佇列尾部彈出下標 j,直到a[j]>a[i],把i入隊尾


(3)對頭下標top為i-w時,要彈出對頭,因為此時對頭已過期。





public class SlideWindow {
    public int[] slide(int[] arr, int n, int w) {
        // write code here
        if(arr==null||n<1||w<1) return null;
        Deque<Integer> qmax=new LinkedList();//雙端佇列,對頭儲存視窗內最大值位置,隊尾就是對下標更新,確保能夠包含後面出現的更小的值
        int res[]=new int[n-w+1];
        int index=0;
        for(int i=0;i<n;i++){//對於每個元素
            while(!qmax.isEmpty()&&arr[qmax.peekLast()]<=arr[i]){//佇列不空,彈出隊尾j,a[j]>a[i]的時候,
                qmax.pollLast();
            }
            qmax.add(i);//如果佇列為空,直接將下標i入隊
            if(qmax.peekFirst()==i-w){//對頭元素為視窗內最大值下標,但若==i-w,說明該對頭不在視窗內,要彈出
                qmax.pollFirst();
            }
            if(i>=w-1){//儲存視窗的最大值
                res[index++]=arr[qmax.peekFirst()];
            }
        }
        return res;
    }
}



2.陣列變樹問題

對於一個沒有重複元素的整數陣列,請用其中元素構造一棵MaxTree,MaxTree定義為一棵二叉樹,其中的節


點與數元素一一對應,同時對於MaxTree的每棵子樹,它的根的元素值為子樹的最大值。現有一建樹方法,對於數


組中的元素,其在樹中的父親為陣列中它左邊比它大的第一個數和右邊比它大的第一個數中更小的一個。若


兩邊都不存在比大的數,那麼它就是樹根。請設計時間複雜度為O(n)的演算法。給定一個無重複元素的陣列A和它的


大小n,請返回一個陣列,其中每個元素為原陣列中對應位置元素在樹中的父親節的編號,若為根則值為-1。


例如:輸入數字{3,1,4,2}


返回陣列:{2,0,-1,2}


思路:使用result[]記錄對陣列中每個元素的父節點下標,初始值-1。其中父節點的判斷使用輔助棧完成。


首先棧用來查詢陣列中元素x,往左第一個>x的元素下標,往右第一個>x的下標。


上述例子:


(1)當棧空時,直接將元素下標入棧,該棧左邊不存在>它的元素,比如3,此時res[3]=-1


(2)當棧不為空,元素x<棧頂a[top],則下標top對應的元素就是x左邊第一個比他大的。此時res[1]=0;


(3)當棧不為空,x>a[top],則彈出棧top,說明x左邊沒有比他大的,pop出top,將x下標入棧。並且此時說明


a[top]右邊第一個>a[top]的是x. 所以res[top]=x的下標,即:res[0]=2;


最後:下標仍為-1的就是根,然後其他節點指向他們父節點的下標。

[java] view plain copy
  1. class MaxTree {  
  2.   
  3.     //  
  4.     // 使用一個棧 儲存下標,來對比元素  
  5.     // 剛開始的話,將所有父結點的下標初始化為 -1  
  6.     public int[] buildMaxTree(int[] A, int n) {  
  7.         if(A == null || n==0return null;  
  8.         int[] result = new int[n];  
  9.         Stack<Integer> st = new Stack<>();  
  10.   
  11.         //初始狀態下,所有元素的父結點都為 null,對應下標為-1  
  12.         Arrays.fill(result,-1);  
  13.   
  14.         // 迴圈處理每一個元素  
  15.         // 需要一個額外的棧來輔助獲取兩個階段的最大值(兩個階段指左邊最大值和右邊最大值)  
  16.         // 每次判斷一個元素分三種情況  
  17.         //  1. 棧為空,也就是初始狀態,直接將元素下標入棧(此時該元素的父結點下標為預設值 -1)  
  18.         //  2. 棧不為空,同時即將進棧的元素比棧頂元素小,那麼直接將元素進棧同時更新當前元素的  
  19.         //     父結點下標為i(設定父結點為左邊第一個比該元素大的,右側第一個比該元素大的第3步說明)  
  20.         //  3. 棧不為空,同時即將進棧的元素比棧頂元素大,那麼需要將棧頂元素出棧,然後比較  
  21.         //     需要分情況:  
  22.         //     1) 棧頂元素對應父結點下標為null, 說明棧頂元素左側沒有最大值,此時需要更新  
  23.         //        棧頂元素對應父結點下標為當前進棧元素的下標 i,然後將棧頂元素pop 出棧  
  24.         //     2) 棧頂元素對應的父結點下標對應陣列中元素比,i 位置對應陣列元素大,由maxTree 的  
  25.         //        定義,我們必須儲存左側和右側相對較小的那個作為最終結果,所以也更新元素下標為i  
  26.         //        然後將棧頂元素 pop 出棧  
  27.         //     3) 如果不在上面兩種情況範圍內,直接將棧頂元素出棧,然後繼續迴圈執行 3 步驟  
  28.         for(int i=0;i<n;i++){  
  29.             // 比較複雜的核心操作  
  30.             while(!st.isEmpty() && A[st.peek()]<A[i]){  
  31.                 // 如果即將《被pop 元素》的左側最大值為null 或者其左側最大值比右側(A[i])最大值大  
  32.                 // 需要先更新該元素的父結點為右側結點下標  
  33.                 if(result[st.peek()] == -1 || A[result[st.peek()]]>A[i]){  
  34.                     result[st.peek()] = i;//更新成右側最大值  
  35.                     System.out.println("更新右側");  
  36.                 }  
  37.   
  38.                 // 不管更新沒更新棧頂元素最終的父結點下標,接下來都需要把其pop 出棧  
  39.                 System.out.println("出棧"+st.pop());  
  40.             }  
  41.   
  42.             // 經過上面迴圈,判斷一下棧是否為空了  
  43.             if(!st.isEmpty()){  
  44.                 //如果不空,更新當前元素左側第一個比它大的父結點為棧頂元素  
  45.                 System.out.println("棧頂元素"+st.peek()+"作為result陣列中"+i);  
  46.                 result[i] = st.peek();  
  47.             }  
  48.             //不管棧是否為空,最終都要將當前元素進棧  
  49.             System.out.println("入棧"+i);  
  50.             st.push(i);  
  51.         }  
  52.         return result;  
  53.     }  
  54. }  


相關文章