1. 程式人生 > >【LeetCode題解】225_用佇列實現棧(Implement-Stack-using-Queues)

【LeetCode題解】225_用佇列實現棧(Implement-Stack-using-Queues)

目錄

更多 LeetCode 題解筆記可以訪問我的 github

@

描述

使用佇列實現棧的下列操作:

  • push(x) -- 元素 x 入棧
  • pop() -- 移除棧頂元素
  • top() -- 獲取棧頂元素
  • empty() -- 返回棧是否為空

注意:

  • 你只能使用佇列的基本操作-- 也就是 push to back, peek/pop from front, size, 和 is empty
    這些操作是合法的。
  • 你所使用的語言也許不支援佇列。 你可以使用 list 或者 deque(雙端佇列)來模擬一個佇列 , 只要是標準的佇列操作即可。
  • 你可以假設所有操作都是有效的(例如, 對一個空的棧不會呼叫 pop 或者 top 操作)。

解法一:雙佇列,入快出慢

思路

為了實現棧這種資料結構後入先出(last in first out, LIFO)的效果,解法一藉助於兩個佇列。其中,一個佇列儲存棧的所有元素(設為佇列1 q1),另一個佇列用於輔助實現入棧、出棧的效果(設為佇列2 q2)。相關操作的底層實現細節見下面對應的小節。

入棧(push)

入棧時,直接將新的元素 x

壓入佇列1 q1 的隊尾(rear),並且用變數 top 儲存棧頂元素,方便後面的檢視棧頂元素(peek)操作,具體的實現步驟見圖1。

在這裡插入圖片描述

圖1:將一個元素壓入棧

程式碼(Java)實現如下:

/** Push element x onto stack. */
public void push(int x) {
    top = x;
    q1.add(x);
}

複雜度分析如下:

  • 時間複雜度:$ O(1) $
  • 空間複雜度:$ O(1) $

出棧(pop)

由於入棧時直接將元素入隊到佇列1 q1 中,因此,棧頂的元素位於佇列1 q1 的尾部。為了能將棧頂元素(佇列1 q1 尾部的元素)彈出,必須先將佇列1 q1 隊尾之前的元素出隊。這裡,我們藉助另一個佇列(輔助佇列 q2)實現這一過程——將佇列1 q1 隊尾之前的元素出隊併入隊到佇列2 q2 中。 之後,將佇列1 q1 中唯一個元素(棧頂元素)出隊。最後,再將兩個佇列的引用進行交換即可完成出棧操作。具體的實現步驟如圖2所示。

在這裡插入圖片描述

圖2:將一個元素出棧

程式碼(Java)實現如下:

/** Removes the element on top of the stack and returns that element. */
public int pop() {
    if (q1.size() == 0) {
        throw new NoSuchElementException("[ERROR] The queue is empty!");
    }

    while (q1.size() > 1) {
        top = q1.remove();
        q2.add(top);
    }
    int res = q1.remove();

    Queue<Integer> temp = q1;
    q1 = q2;
    q2 = temp;

    return res;
}

複雜度分析如下:

  • 時間複雜度:$ O(n) $,其中 \(n\) 表示未出棧前元素的數目。出棧操作需要從佇列1 q1 出隊 \(n\) 個元素,同時入隊 \(n-1\) 個元素到佇列2 q2,因此需要 \(2n - 1\) 次操作。因此 LinkedList 的新增和刪除操作的時間複雜度是 \(O(1)\) 的,因此,總的時間複雜度為 \(O(n)\)
  • 空間複雜度:$ O(1) $

檢視棧頂元素(peek)

因為我們用變數 top 儲存了棧頂的元素,因此只需要返回該變數即可,程式碼(Java)實現如下:

/** Get the top element. */
public int top() {
    return top;
}

複雜度分析如下:

  • 時間複雜度:$ O(1) $
  • 空間複雜度:$ O(1) $

是否為空(empty)

佇列1 q1 中儲存了棧中的所有元素,因此,如果想要知道棧是否為空,只需要判斷佇列1 q1 中是否還有元素,程式碼(Java)實現如下:

/** Returns whether the stack is empty. */
public boolean empty() {
    return q1.isEmpty();
}

複雜度分析如下:

  • 時間複雜度:$ O(1) $
  • 空間複雜度:$ O(1) $

Java 實現

import java.util.NoSuchElementException;
import java.util.LinkedList;
import java.util.Queue;

class MyStack {

    /**
     * The main queue using to store all the elements in the stack
     */
    private Queue<Integer> q1;
    /**
     * The auxiliary queue using to implement `pop` operation
     */
    private Queue<Integer> q2;
    /**
     * The top element in the stack
     */
    private int top;

    /** Initialize your data structure here. */
    public MyStack() {
        q1 = new LinkedList<>();
        q2 = new LinkedList<>();
    }

    /** Push element x onto stack. */
    public void push(int x) {
        top = x;
        q1.add(x);
    }

    /** Removes the element on top of the stack and returns that element. */
    public int pop() {
        if (q1.size() == 0) {
            throw new NoSuchElementException("[ERROR] The stack is empty!");
        }

        while (q1.size() > 1) {
            top = q1.remove();
            q2.add(top);
        }
        int res = q1.remove();

        Queue<Integer> temp = q1;
        q1 = q2;
        q2 = temp;

        return res;
    }

    /** Get the top element. */
    public int top() {
        return top;
    }

    /** Returns whether the stack is empty. */
    public boolean empty() {
        return q1.isEmpty();
    }
}

Python 實現

from collections import deque

class MyStack:

    def __init__(self):
        """
        Initialize your data structure here.
        """
        self._q1, self._q2, self._top = deque(), deque(), None
        

    def push(self, x):
        """
        Push element x onto stack.
        :type x: int
        :rtype: void
        """
        self._top = x
        self._q1.append(x)
        

    def pop(self):
        """
        Removes the element on top of the stack and returns that element.
        :rtype: int
        """
        if not self._q1:
            raise Exception("[ERROR] The stack is empty!")
            
        while len(self._q1) > 1:
            self._top = self._q1.popleft()
            self._q2.append(self._top)
        res = self._q1.popleft()
        
        self._q1, self._q2 = self._q2, self._q1
        return res
        

    def top(self):
        """
        Get the top element.
        :rtype: int
        """
        return self._top
        

    def empty(self):
        """
        Returns whether the stack is empty.
        :rtype: bool
        """
        return not self._q1

解法二:雙佇列,入慢出快

思路

與解法一相同的是,解法二也藉助於兩個佇列。不同之處在於解法二在入棧時,已經在佇列中將元素排列成出棧的順序。因此,解法二實現的棧的入棧操作是 \(O(n)\) 的時間複雜度,而出棧操作則只需要 \(O(1)\) 的時間複雜度。相關操作的底層實現細節見下面對應的小節。

入棧(push)

為了使得佇列1 q1 中的出隊順序和出棧順序是一致的,需要藉助另一個佇列(輔助佇列 q2)。每次有新的元素壓入棧時,將該元素入隊到佇列2 q2 中。接著,將佇列1 q1 中的所有元素出隊併入隊到佇列2 q2 中。最後,再將兩個佇列的引用進行交換,則佇列1 q1 中出隊的順序即為實際的出棧順序。具體的操作步驟如圖3所示。

在這裡插入圖片描述

圖3:將一個元素壓入棧

程式碼(Java)實現如下:

/** Push element x onto stack. */
public void push(int x) {
    q2.add(x);
    while (!q1.isEmpty()) {
        q2.add(q1.remove());
    }

    Queue<Integer> temp = q1;
    q1 = q2;
    q2 = temp;
}

複雜度分析如下:

  • 時間複雜度:\(O(n)\),其中 \(n\) 表示入棧前元素的數目。入棧操作需要 \(n+1\) 個入隊操作,同時還需要 \(n\) 個出隊操作,因此,總共需要 \(2n + 1\) 個操作。由於 LinkedList 的新增和刪除操作的時間複雜度是 \(O(1)\) 的,因此,總的時間複雜度是 \(O(n)\)
  • 空間複雜度:\(O(1)\)

出棧(pop)

由於在入棧時已經將佇列中的元素排列成出棧的順序,因此,只需要出隊佇列1 q1 中隊首的元素即可。

在這裡插入圖片描述

圖4:將一個元素出棧

程式碼(Java)實現如下:

/** Removes the element on top of the stack and returns that element. */
public int pop() {
    if (q1.isEmpty()) {
        throw new NoSuchElementException("[ERROR] The stack is empty!");
    }

    return q1.remove();
}

複雜度分析如下:

  • 時間複雜度:$ O(1) $
  • 空間複雜度:$ O(1) $

檢視棧頂元素(peek)

同理,只需要返回佇列1 q1 隊首元素即可。

/** Get the top element. */
public int top() {
    if (q1.isEmpty()) {
        throw new NoSuchElementException("[ERROR] The stack is empty!");
    }

    return q1.peek();
}

複雜度分析如下:

  • 時間複雜度:$ O(1) $
  • 空間複雜度:$ O(1) $

是否為空(empty)

這個操作和解法一的沒什麼不同,故不再贅言。

Java 實現

import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.Queue;

class MyStack {
    private Queue<Integer> q1;
    private Queue<Integer> q2;

    /** Initialize your data structure here. */
    public MyStack() {
        q1 = new LinkedList<>();
        q2 = new LinkedList<>();
    }

    /** Push element x onto stack. */
    public void push(int x) {
        q2.add(x);
        while (!q1.isEmpty()) {
            q2.add(q1.remove());
        }

        Queue<Integer> temp = q1;
        q1 = q2;
        q2 = temp;
    }

    /** Removes the element on top of the stack and returns that element. */
    public int pop() {
        if (q1.isEmpty()) {
            throw new NoSuchElementException("[ERROR] The stack is empty!");
        }

        return q1.remove();
    }

    /** Get the top element. */
    public int top() {
        if (q1.isEmpty()) {
            throw new NoSuchElementException("[ERROR] The stack is empty!");
        }

        return q1.peek();
    }

    /** Returns whether the stack is empty. */
    public boolean empty() {
        return q1.isEmpty();
    }
}

Python 實現

from collections import deque

class MyStack:

    def __init__(self):
        """
        Initialize your data structure here.
        """
        self._q1, self._q2 = deque(), deque()
        

    def push(self, x):
        """
        Push element x onto stack.
        :type x: int
        :rtype: void
        """
        self._q2.append(x)
        while self._q1:
            self._q2.append(self._q1.popleft())
        self._q1, self._q2 = self._q2, self._q1
        

    def pop(self):
        """
        Removes the element on top of the stack and returns that element.
        :rtype: int
        """
        if not self._q1:
            raise Exception("[ERROR] The stack is empty!")
        return self._q1.popleft()

    def top(self):
        """
        Get the top element.
        :rtype: int
        """
        if not self._q1:
            raise Exception("[ERROR] The stack is empty!")
        return self._q1[0]
        

    def empty(self):
        """
        Returns whether the stack is empty.
        :rtype: bool
        """
        return not self._q1

解法三:單佇列

思路

上面兩種解法都藉助於兩個佇列,實際上,只借助於一個佇列也可以實現棧的先入先出效果。

入棧(push)

入棧時,新新增的元素位於佇列的隊尾,但是對於棧而言,它其實是棧頂元素。為了使得新新增的元素位於隊首,可以將其之前的所有元素出隊並重新入隊。最終,佇列中元素的順序和出棧的順序是一致的。具體的操作步驟如下圖所示。

在這裡插入圖片描述

圖5:將一個元素壓入棧

程式碼(Java)實現如下:

/** Push element x onto stack. */
public void push(int x) {
    queue.add(x);
    for (int i = 0; i < queue.size() - 1; ++i) {
        queue.add(queue.remove());
    }
}

複雜度分析:

  • 時間複雜度:\(O(n)\),其中 \(n\) 表示入棧前棧內元素的數目。入棧操作需要 \(n\) 次的出隊操作,同時也需要 \(n + 1\)次的入隊操作,因此,需要總的操作次數為 \(2n + 1\) 次。由於 LinkedList 的新增和刪除操作的時間複雜度是 \(O(1)\) 的,因此,總的時間複雜度為 \(O(n)\)
  • 空間複雜度:\(O(1)\)

出棧(pop)

由於在入棧時已經將佇列中的元素排列成出棧的順序,因此,只需要出隊佇列 q1 中隊首的元素即可。

在這裡插入圖片描述

圖6:將一個元素出棧

程式碼(Java)實現如下:

/** Removes the element on top of the stack and returns that element. */
public int pop() {
    if (queue.isEmpty()) {
        throw new NoSuchElementException("[ERROR] The stack is empty!");
    }

    return queue.remove();
}

複雜度分析如下:

  • 時間複雜度:$ O(1) $
  • 空間複雜度:$ O(1) $

檢視棧頂元素(peek)

同理,只需要返回佇列 q1 的隊首元素即可。

/** Get the top element. */
public int top() {
    if (queue.isEmpty()) {
        throw new NoSuchElementException("[ERROR] The stack is empty!");
    }

    return queue.peek();
}

複雜度分析如下:

  • 時間複雜度:$ O(1) $
  • 空間複雜度:$ O(1) $

是否為空(empty)

佇列 q1 中儲存了棧中的所有元素,因此,如果想要知道棧是否為空,只需要判斷佇列 q1 中是否還有元素,程式碼(Java)實現如下:

/** Returns whether the stack is empty. */
public boolean empty() {
    return queue.isEmpty();
}

複雜度分析如下:

  • 時間複雜度:$ O(1) $
  • 空間複雜度:$ O(1) $

Java 實現

import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.Queue;

class MyStack {
    private Queue<Integer> queue;

    /** Initialize your data structure here. */
    public MyStack() {
        queue = new LinkedList<>();
    }

    /** Push element x onto stack. */
    public void push(int x) {
        queue.add(x);
        for (int i = 0; i < queue.size() - 1; ++i) {
            queue.add(queue.remove());
        }
    }

    /** Removes the element on top of the stack and returns that element. */
    public int pop() {
        if (queue.isEmpty()) {
            throw new NoSuchElementException("[ERROR] The stack is empty!");
        }

        return queue.remove();
    }

    /** Get the top element. */
    public int top() {
        if (queue.isEmpty()) {
            throw new NoSuchElementException("[ERROR] The stack is empty!");
        }

        return queue.peek();
    }

    /** Returns whether the stack is empty. */
    public boolean empty() {
        return queue.isEmpty();
    }
}

/**
 * Your MyStack object will be instantiated and called as such:
 * MyStack obj = new MyStack();
 * obj.push(x);
 * int param_2 = obj.pop();
 * int param_3 = obj.top();
 * boolean param_4 = obj.empty();
 */

Python 實現

from collections import deque

class MyStack:

    def __init__(self):
        """
        Initialize your data structure here.
        """
        self._q = deque()
        

    def push(self, x):
        """
        Push element x onto stack.
        :type x: int
        :rtype: void
        """
        self._q.append(x)
        for _ in range(len(self._q) - 1):
            self._q.append(self._q.popleft())
        

    def pop(self):
        """
        Removes the element on top of the stack and returns that element.
        :rtype: int
        """
        if not self._q:
            raise Exception("[ERROR] The stack is empty!")
        return self._q.popleft()

    def top(self):
        """
        Get the top element.
        :rtype: int
        """
        if not self._q:
            raise Exception("[ERROR] The stack is empty!")
        return self._q[0]
        

    def empty(self):
        """
        Returns whether the stack is empty.
        :rtype: bool
        """
        return not self._q