更多 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\) 個元素到隊列2q2
,因此需要 \(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