算法圖解:如何找出棧中的最小值?

前面咱們學習了不少關於棧的知識,好比《動圖演示:手擼堆棧的兩種實現方法!》《JDK 居然是這樣實現棧的?》,那麼接下來咱們再來刷一些關於棧的經典面試題以鞏固學過的知識。java

咱們今天的面試題是這樣的...面試

題目

定義棧的數據結構,請在該類型中實現一個可以獲得棧的最小元素的 min 函數在該棧中,調用 min、push 及 pop 的時間複雜度都是 O(1)。數組

示例:數據結構

MinStack minStack = new MinStack();

minStack.push(-2);函數

minStack.push(0);性能

minStack.push(-3);學習

minStack.min();   --> 返回 -3.測試

minStack.pop();spa

minStack.top();      --> 返回 0.3d

minStack.min();   --> 返回 -2.

LeetCode 地址: https://leetcode-cn.com/problems/bao-han-minhan-shu-de-zhan-lcof/

思考

首先來講這道題目自己很好理解,它的實現難點在於如下兩個方面:

  • 當咱們進行 pop(移除棧頂元素)操做時若是刪除的是當前最小值,那麼咱們如何尋找下一個最小值?
  • 要保證調用 min、push 及 pop 的時間複雜度都是 O(1)。

也就是說,在咱們執行了 pop 時若是移除的棧中最小的值,那麼如何尋找棧中的下一個最小元素?而且要保證操做的時間複雜度爲 O(1)。這個時間複雜度制約了咱們在移除了最小值以後不能經過遍歷查找下一個最小值,因此這就成爲了這道題的難點。

好比當咱們移除如下棧頂元素值:
image.png
由於最小值就是 1,所以在移除以後最小值也被移除了,以下圖所示:
image.png
那麼接下來,讓咱們一塊兒思考 3 分鐘,想想應該如何處理這個問題~

解題思路

其實咱們能夠在每次入棧時,判斷當前元素是否小於最小值,若是小於則將原最小值和最新的最小值相繼入棧,這樣在調用 pop 時即便移除的是最小值,咱們也能經過獲取下一個元素獲得一個新的最小值,執行流程以下所示。

操做步驟1

入棧第一個元素,由於是第一個元素,所以最小值就是此元素的值。
image.png

操做步驟2

入棧第二個元素,以下圖所示:
image.png
由於入棧的元素 3 比 8 小,因此先將棧中的原最小值 8 存入棧中,再將 3 入棧。

操做步驟3

入棧第三個元素,以下圖所示:
image.png
由於入棧元素 5 大於 3,所以棧中的最小值不變,直接將元素 5 入棧。

操做步驟4

繼續入棧,以下圖所示:
image.png
入棧元素 1 小於 3,所以先將原最小值 3 入棧,再將 1 入棧,棧中的最小值更改成 1。

操做步驟5

執行出棧操做,以下圖所示:
image.png
元素 1 出棧,判斷當前元素就是棧的最小值,所以將棧頂元素 3 設置爲最小值,並移除元素 3,以下圖所示:

image.png

操做步驟6

繼續出棧,以下圖所示:
image.png
由於元素 5 不是當前最小值,所以直接出棧。

操做步驟7

繼續出棧,以下圖所示:
image.png
由於出棧元素 3 爲最小值,所以繼續將最小值設置爲棧頂元素 8,並將棧頂元素出棧,以下圖所示:
image.png
這樣就剩下最後一個元素了,最後一個元素出棧以後就成空棧了,整個流程就執行完了。

實現代碼1

接下來咱們將上面的思路用代碼實現一下,咱們用數組實現的棧來實現相關的功能,代碼以下:

class MinStack {
    private int[] data; // 棧數據
    private int maxSize; // 最大長度
    private int top; // 棧頂指針(下標)
    private int min; // 最小值

    // 構造函數
    public MinStack() {
        // 設置默認值
        maxSize = 10000;
        data = new int[maxSize];
        top = -1;
        min = Integer.MAX_VALUE;
    }

    // 入棧(添加元素)
    public void push(int x) {
        if (min >= x) {
            // 遇到了更小的值,記錄原最小值(入棧)
            data[++top] = min;
            min = x;
        }
        // 當前值入棧
        data[++top] = x;
    }

    // 出棧(移除棧頂元素)
    public void pop() {
        if (min == data[top]) {
            min = data[--top]; // 拿到原最小值,並(將原最小值)出棧
        }
        --top; // 出棧
    }

    // 查找棧頂元素
    public int top() {
        return data[top];
    }
    
    // 查詢最小值
    public int min() {
        return min;
    }
}

上述代碼在 LeetCode 的執行結果以下:

image.png

能夠看出性能仍是很高的,超越了 99.92% 的用戶,內存消耗也不大。它的核心代碼在 push 方法內,先將原最小值和最新最小值相繼入棧,在 pop 出棧時判斷出棧元素是否爲最小值,若是是最小值則將當前最小值指向棧頂元素並將棧頂元素出棧,這樣就獲得了下一個新的最小值了。

實現代碼2

若是咱們不想使用數組的自定義棧來實現,還能夠使用 Java 中自帶的棧 Stack 來實現此功能,代碼以下:

class MinStack {
    private Stack<Integer> stack = new Stack<>();
    private int min = Integer.MAX_VALUE;

    public MinStack() { }

    // 入棧(添加元素)
    public void push(int x) {
        if (x <= min) {
            // 遇到了更小的值,記錄原最小值(入棧)
            stack.push(min);
            min = x;
        }
        stack.push(x);
    }

    // 出棧(移除棧頂元素)
    public void pop() {
        if (stack.pop() == min) {
            min = stack.pop(); // 取出原最小值
        }
    }

    // 查找棧頂元素
    public int top() {
        return stack.peek();
    }

    // 查詢最小值
    public int min() {
        return min;
    }
}

上述代碼在 LeetCode 的執行結果以下:

image.png

從結果能夠看出,使用 Java 中自帶的棧的性能不如自定義數組的棧,但代碼仍是經過了測試。這種實現方式的優勢就是代碼比較簡單,能夠利用了 Java 自身的 API 來完成了最小值的查找。

這種實現代碼的方式(使用 Java API),在刷題或者實際面試中若是沒有特殊說明是能夠直接用的。

總結

本文咱們經過兩種方式:自定義數組棧和 Java API 中的 Stack 來實現了棧中最小值的功能,保證了在調用棧的 min、push 及 pop 方法時的時間複雜度都是 O(1)。兩種實現方式的代碼雖然略不相同,但實現思路都是同樣的,都是在元素入棧時判斷當前元素是否小於最小元素,若是小於最小元素則先將原最小值入棧,再將當前最小元素入棧,這樣當調用 pop 方法時,即便移除的是最小值,只須要將下一個元素取出即爲新的最小值了,這樣就能夠實現調用 min、push 及 pop 方法時的時間複雜度都是 O(1) 了。

最後

機智的你必定還有其餘的實現答案,評論區告訴我吧~

原創不易,各位素質四連,謝啦。

文末福利:搜索公衆號「Java中文社羣」發送「面試」,領取最新的面試資料。

image.png

相關文章
相關標籤/搜索