數(shù)據(jù)結(jié)構(gòu)設(shè)計 --- 棧實現(xiàn)隊列、隊列實現(xiàn)棧

隊列實現(xiàn)棧(leetcode225)

題目描述:請你僅使用兩個隊列實現(xiàn)一個后入先出(LIFO)的棧,并支持普通隊列的全部四種操作(push、top、pop 和 empty)。

實現(xiàn) MyStack 類:

  • void push(int x) 將元素 x 壓入棧頂。
  • int pop() 移除并返回棧頂元素。
  • int top() 返回棧頂元素。
  • boolean empty() 如果棧是空的,返回 true ;否則,返回 false 。

注意:

  • 你只能使用隊列的基本操作 —— 也就是 push to back、peek/pop from front、size 和 is empty 這些操作。
    你所使用的語言也許不支持隊列。 你可以使用 list (列表)或者 deque(雙端隊列)來模擬一個隊列 , 只要是標(biāo)準(zhǔn)的隊列操作即可。

示例

輸入:
["MyStack", "push", "push", "top", "pop", "empty"]
[[], [1], [2], [], [], []]
輸出:
[null, null, null, 2, 2, false]

解釋:
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.top(); // 返回 2
myStack.pop(); // 返回 2
myStack.empty(); // 返回 False

思路

  • 定義兩個隊列,一個主隊列,一個輔助隊列,模擬棧后進(jìn)先出。
  • 當(dāng)入棧操作時,先將該數(shù)據(jù)入隊輔助隊列,然后主隊列內(nèi)容導(dǎo)入輔助隊列(while循環(huán)),最后將將主隊列和輔助隊列進(jìn)行交換。
  • 其實,了解了上述思路(將當(dāng)前加入元素加到已經(jīng)按照棧排好的元素最前邊),我們只需要一個隊列即可。

復(fù)雜度分析

  • 時間復(fù)雜度:入棧操作 O(n),其余操作都是 O(1)。

    入棧操作需要將queue 1中的 n個元素出隊,并入隊 n+1 個元素到queue 2 ,共有 2n+1 次操作,每次出隊和入隊操作的時間復(fù)雜度都是 O(1),因此入棧操作的時間復(fù)雜度是 O(n)。

    出棧操作和獲取棧頂元素對應(yīng)將 queue 1的前端元素出隊,時間復(fù)雜度是 O(1)。

    判斷棧是否為空操作只需要判斷queue 1是否為空,時間復(fù)雜度是 O(1)。

  • 空間復(fù)雜度:O(n),其中 n 是棧內(nèi)的元素。需要使用兩個隊列存儲棧內(nèi)的元素。

代碼實現(xiàn)

class MyStack {

    private Queue<Integer> queue1;
    private Queue<Integer> queue2;

    public MyStack() {
        queue1 = new LinkedList<>();
        queue2 = new LinkedList<>();
    }
    
    public void push(int x) {
        queue2.offer(x);
        // 特別注意,while循環(huán)
        while (!queue1.isEmpty()) {
            queue2.offer(queue1.poll());
        }
        Queue<Integer> temp = queue1;
        queue1 = queue2;
        queue2 = temp;
    }
    
    public int pop() {
        return queue1.poll();
    }
    
    public int top() {
        return queue1.peek();
    }
    
    public boolean empty() {
        return queue1.isEmpty();
    }
}

使用一個隊列實現(xiàn)(將當(dāng)前加入的元素放在已經(jīng)是棧順序序列的最前邊,實現(xiàn)后進(jìn)先出原則而)

class MyStack {

    private Queue<Integer> queue;

    public MyStack() {
        queue = new LinkedList<>();
    }
    
    public void push(int x) {
        queue.offer(x);
        int cnt = queue.size();
        while (cnt-- > 1) {
            queue.offer(queue.poll());
        }
    }
    
    public int pop() {
        return queue.poll();
    }
    
    public int top() {
        return queue.peek();
    }
    
    public boolean empty() {
        return queue.isEmpty();
    }
}

棧實現(xiàn)隊列(leetcode232)

問題描述:請你僅使用兩個棧實現(xiàn)先入先出隊列。隊列應(yīng)當(dāng)支持一般隊列支持的所有操作(push、pop、peek、empty):實現(xiàn) MyQueue 類:

  • void push(int x) 將元素 x 推到隊列的末尾
  • int pop() 從隊列的開頭移除并返回元素
  • int peek() 返回隊列開頭的元素
  • boolean empty() 如果隊列為空,返回 true ;否則,返回 false

說明:

  • 你只能使用標(biāo)準(zhǔn)的棧操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。你所使用的語言也許不支持棧。你可以使用 list 或者 deque(雙端隊列)來模擬一個棧,只要是標(biāo)準(zhǔn)的棧操作即可。

要求

  • 你能否實現(xiàn)每個操作均攤時間復(fù)雜度為 O(1) 的隊列?換句話說,執(zhí)行 n 個操作的總時間復(fù)雜度為 O(n) ,即使其中一個操作可能花費較長時間。

示例

輸入:
["MyQueue", "push", "push", "peek", "pop", "empty"]
[[], [1], [2], [], [], []]
輸出:
[null, null, null, 1, 1, false]

解釋:
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1
myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false

思路

  • 定義兩個棧用于數(shù)據(jù)的輸入和輸出。
  • 當(dāng)進(jìn)行pop和poll操作時,如果輸出棧為空,那么將輸入棧數(shù)據(jù)全部移到輸出棧。所以,當(dāng)移到輸出棧中然后進(jìn)行輸出的時候(出隊列),就滿足先進(jìn)先出的結(jié)構(gòu)。

復(fù)雜度分析

  • 時間復(fù)雜度:push 和empty 為 O(1),pop 和 peek 為均攤 O(1)。對于每個元素,至多入棧和出棧各兩次,故均攤復(fù)雜度為 O(1)。
  • 空間復(fù)雜度:O(n)O(n)。其中 n是操作總數(shù)。對于有 n 次 push 操作的情況,隊列中會有 n 個元素,故空間復(fù)雜度為 O(n)。

代碼實現(xiàn)

class MyQueue {

    private Deque<Integer> inStack;
    private Deque<Integer> outStack;

    public MyQueue() {
        inStack = new LinkedList<>();
        outStack = new LinkedList<>();
    }
    
    public void push(int x) {
        inStack.push(x);
    }
    
    // 獲取棧中的元素時,需要檢查輸出棧是否為空
    public int pop() {
        if (outStack.isEmpty()) {
            inToOut();
        }
        return outStack.pop();
    }
    
    public int peek() {
        if (outStack.isEmpty()) {
            inToOut();
        }
        return outStack.peek();
    }
    
    public boolean empty() {
        return inStack.isEmpty() && outStack.isEmpty();
    }
    
    // 將輸入棧中的元素移到輸出棧
    private void inToOut() {
        while (!inStack.isEmpty()) {
            outStack.push(inStack.pop());
        }
    }
}

對于均攤復(fù)雜度的說明:參考@宮水三葉

我們先用另外一個例子來理解「均攤復(fù)雜度」,大家都知道「哈希表」底層是通過數(shù)組實現(xiàn)的。

  • 正常情況下,計算元素在哈希桶的位置,然后放入哈希桶,即插入操作復(fù)雜度為 O(1),假定是通過簡單的“拉鏈法”搭配「頭插法」方式來解決哈希沖突。
  • 但當(dāng)某次元素插入后,「哈希表」達(dá)到擴(kuò)容閾值,則需要對底層所使用的數(shù)組進(jìn)行擴(kuò)容,這個復(fù)雜度是 O(n)
  • 顯然「擴(kuò)容」操作不會發(fā)生在每一次的元素插入中,因此擴(kuò)容的 O(n) 都會伴隨著 n 次的 O(1),也就是 O(n) 的復(fù)雜度會被均攤到每一次插入當(dāng)中,因此哈希表插入仍然是 O(1)的。

同理,我們不是發(fā)生在每一次的「輸出操作」中,而是集中發(fā)生在一次「輸出棧為空」的時候,因此 pop 和 peek 都是均攤復(fù)雜度為 O(1)的操作。

補(bǔ)充:Queue接口分析:add和offer區(qū)別,remove和poll方法到底啥區(qū)別

這些方法的主要區(qū)別在于,如果執(zhí)行失敗,是拋出異常還是返回值。

  • queue的增加元素方法add和offer的區(qū)別在于,add方法在隊列滿的情況下將選擇拋異常的方法來表示隊列已經(jīng)滿了,而offer方法通過返回false表示隊列已經(jīng)滿了;在有限隊列的情況,使用offer方法優(yōu)于add方法;
  • remove方法和poll方法都是刪除隊列的頭元素,remove方法在隊列為空的情況下將拋異常,而poll方法將返回null;
  • element和peek方法都是返回隊列的頭元素,但是不刪除頭元素,區(qū)別在與element方法在隊列為空的情況下,將拋異常,而peek方法將返回null

總結(jié):我們一般希望拋出返回值,優(yōu)先使用offer,poll和peek方法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容