找出數組中每個數右邊第一個比它大的元素--時間複雜度o(n)單調棧解法

題目:給定一個整型數組,數組元素隨機無序的,要求打印出所有元素右邊第一個大於該元素的值。

如數組A=[1,5,3,6,4,8,9,10] 輸出[5, 6, 6, 8, 8, 9, 10, -1]

如數組A=[8, 2, 5, 4, 3, 9, 7, 2, 5] 輸出[9, 5, 9, 9, 9, -1, -1, 5, -1]

1、暴力遍歷

我們很容易想到複雜度爲O(n^2)的解法,遍歷數組中的每一個後面所有元素,找到第一個大於它的,輸出即可。代碼實現如下:

public int[] findMaxRight(int[] array) { 
        if(array == null) {
            return array;
        }
        int size = array.length;
        int[] result = new int[size];
        for(int i = 0; i < size-1; i++) {
            for(int j = i+1; j < size; j++) {
                if(array[j] > array[i]) {
                    result[i] = array[j];
                    break;
                }
            }
        }
        result[size-1] = -1;//最後一個元素右邊沒有元素,所以肯定爲-1
        return result;
    }

2、藉助棧,時間複雜度O(n)

如果要求時間複雜度爲O(n)呢,能不能遍歷一次數組中元素,就可以找到右邊第一個大於它的元素呢,當然是可行的,不過要藉助數據結構棧。

具體思路是:我們用棧來保存未找到右邊第一個比它大的元素的索引(保存索引是因爲後面需要靠索引來給新數組賦值),初始時,棧裏放的是第一個元素的索引0值。

步驟如下:

(1)初始化棧,裏面爲第一個元素索引0值;

(2)遍歷到下一個元素A[i]

1) 如果棧不爲空且當前遍歷的元素值A[i]大於棧頂的元素值A[stack.peek()],說明當前元素正好是棧頂元素右邊第一個比它大的元素,將棧頂元素彈出,result[stack.pop()]=A[i]

繼續遍歷的元素值A[i]是否大於新棧頂元素值A[stack.peek()],如果大於,說明A[i]也是比A[stack.peek()]右邊第一個比它大的元素,將棧頂元素彈出,result[stack.pop()]=A[i],一直循環,直到不滿足條件1),即棧頂爲空或是當前遍歷的元素值小於棧頂元素索引處的值。

2) 如果棧爲空,說明前面的元素都找到了比它右邊大的元素,則直接將當前元素的索引放入棧中;

3)如果當前遍歷的元素值A[i]小於棧頂元素索引的值A[stack.peek()],說明還未找到棧頂元素中右邊第一個比它大的元素,直接將當前遍歷的元素的索引入棧即可stack.push(i);

將i++,重複步驟(2)

(3)直到遍歷完所有元素,如果棧不爲空,說明棧中保存的全是未找到右邊第一個比它大的數組索引,我們依次將這些棧元素出棧,並賦值result[stack.pop()]=-1即可。

以A=[8, 2, 5, 4, 3, 9, 7, 2, 5] 舉例

1) 初始棧頂元素數組的第一個索引0,棧頂A[stack.peek()]=A[0]=8

2) 遍歷到下一個元素值爲2,它比棧頂A[stack.peek()]=A[0]=8元素值小,即上面第(2)步中的第3)種情況,直接將該元素的索引入棧,棧中元素是1, 0, 棧頂A[stack.peek()]=A[1]=2

3) 遍歷到下一個元素值爲5,它比棧頂A[stack.peek()]=A[1]=2元素值大,即上面第(2)步中的第1)種情況,將棧頂元素出棧,並且賦值result[stack.pop()]=result[1]=5,現在棧中還剩下 0, 棧頂A[stack.peek()]=A[0]=8

接着判斷當前遍歷的元素值5是否在大於棧頂A[stack.peek()]=A[0]=8,發現小於棧頂元素,即上面第(2)步中的第3)種情況,直接將該元素的索引入棧,棧中元素是2, 0, 棧頂A[stack.peek()]=A[2]=5

4) 遍歷到下一個元素值爲4,它比棧頂A[stack.peek()]=A[2]=5元素值小,直接將該元素的索引入棧,棧中元素是3,2, 0, 棧頂A[stack.peek()]=A[3]=4

5) 遍歷到下一個元素值爲3,它比棧頂A[stack.peek()]=A[3]=4元素值小,直接將該元素的索引入棧,棧中元素是4,3,2, 0, 棧頂A[stack.peek()]=A[4]=3

6) 遍歷到下一個元素值爲9,它比棧頂A[stack.peek()]=A[4]=3元素值大,將棧頂元素出棧,並且賦值result[stack.pop()]=result[4]=9,現在棧中還剩下3, 2, 0, ....重複這個步驟,最終都因爲9比棧頂元素大,棧中元素出棧,最終result[3]=9, result[2]=9, result[0]=9, 直到棧中元素都出棧了,棧爲空,變成上面的(2) 3) 情況,直接將當前元素索引值存入棧。即棧頂A[stack.peek()]=A[5]=9

7) 遍歷到下一個元素值爲7,它比棧頂A[stack.peek()]=A[5]=9元素值小,直接將該元素的索引入棧,棧中元素是6,5, 棧頂A[stack.peek()]=A[6]=7

8) 遍歷到下一個元素值爲2,它比棧頂A[stack.peek()]=A[6]=7元素值小,直接將該元素的索引入棧,棧中元素是7, 6,5, 棧頂A[stack.peek()]=A[7]=2

9) 遍歷到下一個元素值爲5,它比棧頂A[stack.peek()]=A[7]=2元素值大,將棧頂元素出棧,並且賦值result[stack.pop()]=result[7]=5,現在棧中還剩下6, 5, 棧頂元素A[stack.peek()]=A[6]=7比當前處理的元素值5大,所以將當前元素的索引值入棧,棧中變成8, 6, 5

由於元素遍歷完了,棧中還保存的元素代表該索引處找不到右邊第一個比它大的元素值了,所以挨個將棧中元素出棧,並賦值result[8]=-1, result[6]=-1, result[5]=-1

輸出[9, 5, 9, 9, 9, -1, -1, 5, -1]

可以看到只需要遍歷一次,就把數組中每個右邊比它大的元素輸出了。

具體代碼實現如下:

public int[] findMaxRightWithStack(int[] array) {
        if(array == null) {
            return array;
        }
        int size = array.length;
        int[] result = new int[size];
        Stack<Integer> stack = new Stack<>();
        stack.push(0);
        int index = 1;
        while(index < size) {
            if(!stack.isEmpty() && array[index] > array[stack.peek()]) {
                result[stack.pop()] = array[index];
            } else {
                stack.push(index);
                index++;
            }
        }
        if(!stack.isEmpty()) {
            result[stack.pop()] = -1;
        }
        return result;
    }

總結:

第二種方是以空間換時間,用到了數據結構棧,用到了單調棧思想,單調棧可以用來解決一類問題,單調棧是指:棧內元素保持一定單調性(單調遞增或單調遞減)的棧。這裏的單調遞增或遞減是指的從棧頂到棧底單調遞增或遞減。既然是棧,就滿足後進先出的特點。與之相對應的是單調隊列。

具體可參考這篇文章:單調棧原理及應用 詳解 附各種類型的題目練習

利用單調棧解決此問題,尤其在數據量特別大的時候,第二種方法帶來的時間上的優勢會非常明顯。

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