【LeetCode題解】232_用棧實現隊列(Implement-Queue-using-Stacks)
目錄
- 描述
- 解法一:在一個棧中維持所有元素的出隊順序
- 思路
- 入隊(push)
- 出隊(pop)
- 查看隊首(peek)
- 是否為空(empty)
- Java 實現
- Python 實現
- 思路
- 解法二:一個棧入,一個棧出
- 思路
- 入隊(push)
- 出隊(pop)
- 查看隊首(peek)
- 是否為空(empty)
- Java 實現
- Python 實現
- 思路
更多 LeetCode 題解筆記可以訪問我的 github。
描述
使用棧實現隊列的下列操作:
- push(x) -- 將一個元素放入隊列的尾部。
- pop() -- 從隊列首部移除元素。
- peek() -- 返回隊列首部的元素。
- empty() -- 返回隊列是否為空。
示例:
MyQueue queue = new MyQueue();
queue.push(1);
queue.push(2);
queue.peek(); // 返回 1
queue.pop(); // 返回 1
queue.empty(); // 返回 false
說明:
- 你只能使用標準的棧操作 -- 也就是只有
push to top
,peek/pop from top
,size
, 和is empty
操作是合法的。 - 你所使用的語言也許不支持棧。你可以使用 list 或者 deque(雙端隊列)來模擬一個棧,只要是標準的棧操作即可。
- 假設所有操作都是有效的 (例如,一個空的隊列不會調用 pop 或者 peek 操作)。
解法一:在一個棧中維持所有元素的出隊順序
思路
隊列是一種先入先出(first in first out, FIFO)的數據結構,而棧是一種後入先出(last in first out, LIFO)的數據結構。因此,如果要使用棧來達到隊列的效果,即用有後入先出性質的數據結構來實現先入先出的效果,需要借用兩個棧來改變元素的出隊順序。當然,借用兩個棧來實現隊列也有不同的實現方式,這一節介紹第一種實現方式,在下一小節介紹第二種方式。
第一種方式是在一個棧中維持所有元素的出隊順序,即所有的元素在入隊操作完成後只會保存在一個棧中,且其出棧的順序和出隊的順序是一致的。下面對入隊、出隊等操作的底層實現分別進行講解。
入隊(push)
為了實現出棧順序和出隊順序是一致的,入棧時必須將新的元素壓入棧底。為了實現這種效果,在入隊時,首先將棧1(假設棧1中保存所有的元素)中所有的元素彈出並壓入棧2中,接著將新的元素壓入棧1中,最後再將棧2中的所有彈出並壓入棧1中。詳細的步驟如圖1所示。
圖1:將一個元素入隊
代碼(Java)實現如下。
public void push(int x) {
// 將棧1中的所有元素彈出並壓入棧2中
while (!s1.isEmpty()) {
s2.push(s1.pop());
}
// 將新的元素壓入棧1
s1.push(x);
// 將棧2的所有元素彈出並壓入棧1
while (!s2.isEmpty()) {
s1.push(s2.pop());
}
}
復雜度分析如下:
- 時間復雜度:\(O(n)\),其中 \(n\) 表示入隊時隊列元素的數目,即棧1中元素的數目。入隊時,棧1中的元素需要進行出棧和入棧兩次,需要 \(4n\) 次操作,再加上新的元素的一次入棧操作,總的操作次數為 \(4n + 1\) 次。由於棧的入棧和出棧的時間復雜度是 \(O(1)\) 的,因此,入隊的時間復雜度是 \(O(n)\) 的
- 空間復雜度:\(O(n)\)
出隊(pop)
出隊操作比較簡單,由於棧1中元素的出棧順序和隊列的出隊順序一致,因此,只需要彈出棧頂元素即可完成出隊操作。
public int pop() {
if (s1.isEmpty()) {
throw new IllegalArgumentException("[ERROR] The queue is empty!");
}
return s1.pop();
}
復雜度分析如下:
- 時間復雜度:\(O(1)\)
- 空間復雜度:\(O(1)\)
查看隊首(peek)
與出隊操作類似,只需要查看棧1棧頂的元素即可完成查看隊首的操作。
public int peek() {
if (s1.isEmpty()) {
throw new IllegalArgumentException("[ERROR] The queue is empty!");
}
return s1.peek();
}
復雜度分析如下:
- 時間復雜度:\(O(1)\)
- 空間復雜度:\(O(1)\)
是否為空(empty)
由於棧1中保存隊列的所有元素,因此只需要判斷棧1是否為空即可知道隊列是否為空。
public boolean empty() {
return s1.isEmpty();
}
復雜度分析如下:
- 時間復雜度:\(O(1)\)
- 空間復雜度:\(O(1)\)
Java 實現
class MyQueue {
private Stack<Integer> s1;
private Stack<Integer> s2;
/** Initialize your data structure here. */
public MyQueue() {
s1 = new Stack<>();
s2 = new Stack<>();
}
/** Push element x to the back of queue. */
public void push(int x) {
while (!s1.isEmpty()) {
s2.push(s1.pop());
}
s1.push(x);
while (!s2.isEmpty()) {
s1.push(s2.pop());
}
}
/** Removes the element from in front of queue and returns that element. */
public int pop() {
if (s1.isEmpty()) {
throw new IllegalArgumentException("[ERROR] The queue is empty!");
}
return s1.pop();
}
/** Get the front element. */
public int peek() {
if (s1.isEmpty()) {
throw new IllegalArgumentException("[ERROR] The queue is empty!");
}
return s1.peek();
}
/** Returns whether the queue is empty. */
public boolean empty() {
return s1.isEmpty();
}
}
Python 實現
class MyQueue:
def __init__(self):
"""
Initialize your data structure here.
"""
self._s1, self._s2 = [], []
def push(self, x):
"""
Push element x to the back of queue.
:type x: int
:rtype: void
"""
while self._s1:
self._s2.append(self._s1.pop())
self._s1.append(x)
while self._s2:
self._s1.append(self._s2.pop())
def pop(self):
"""
Removes the element from in front of queue and returns that element.
:rtype: int
"""
return self._s1.pop()
def peek(self):
"""
Get the front element.
:rtype: int
"""
return self._s1[-1]
def empty(self):
"""
Returns whether the queue is empty.
:rtype: bool
"""
return not self._s1
解法二:一個棧入,一個棧出
思路
解法二的實現方式與解法一有點不同,按照功能的不同,解法二將兩個棧一個用於入隊,一個用於出隊。假設棧 inStack
用於實現入隊操作,棧 outStack
用於實現出隊操作。下面對入隊、出隊等操作的底層實現分別進行講解。
入隊(push)
入隊操作比較簡單,直接將新的元素壓入棧 inStack
中,同時,對於第一個進入棧中的元素,我們用一個變量 front
保存起來,用於表示棧 inStack
這個隊列的隊首。
/** Push element x to the back of queue. */
public void push(int x) {
if (inStack.empty()) {
front = x;
}
inStack.push(x);
}
復雜度分析如下:
- 時間復雜度:\(O(1)\)
- 空間復雜度:\(O(n)\),需要額外的空間用於存儲隊列元素
出隊(pop)
在入隊時,由於先入的元素處於輸入棧 inStack
的棧底,因此,為了能夠彈出棧底的元素實現出隊操作,需要將輸入棧 inStack
中的元素彈出並壓入到輸出棧 outStack
中。此時,輸出棧 outStack
中元素的出棧順序和隊列的出隊順序是一致的。只要輸出棧 outStack
中還有元素,每次執行出隊操作只需要將棧 outStack
的棧頂元素彈出即可。當輸出棧 outStack
為空時,執行出隊操作則需要先將輸入棧 inStack
中的元素彈出並壓入輸出棧。詳細的步驟如圖2所示。
圖2:將一個元素出隊
代碼(Java)實現如下。
/** Removes the element from in front of queue and returns that element. */
public int pop() {
if (empty()) {
throw new IllegalArgumentException("[ERROR] The queue is empty!");
}
if (outStack.isEmpty()) {
while (!inStack.isEmpty()) {
outStack.push(inStack.pop());
}
}
return outStack.pop();
}
復雜度分析如下:
- 時間復雜度:均攤時間復雜度為 \(O(1)\),最壞情況下,時間復雜度為 \(O(n)\),更為詳細的均攤復雜度分析可以查看官網的文章
- 空間復雜度:\(O(1)\)
查看隊首(peek)
與出隊操作類似,當輸出棧 outStack
不為空時,只需要返回輸出棧 outStack
的棧頂元素即可。不同的是,由於我們用變量 front
存儲了輸入棧最先進入的元素,因此,當輸出棧 outStack
為空時,不需要再將輸入棧 inStack
的元素彈出並壓入到輸出棧 outStack
中便可以得到當前隊首的元素。
/** Get the front element. */
public int peek() {
if (empty()) {
throw new IllegalArgumentException("[ERROR] The queue is empty!");
}
if (!outStack.isEmpty()) {
return outStack.peek();
} else {
return front;
}
}
復雜度分析如下:
- 時間復雜度:\(O(1)\),借助於變量
front
,可以使得peek
操作在任意情況下都是 \(O(1)\) 的時間復雜度 - 空間復雜度:\(O(1)\)
是否為空(empty)
由於兩個都有可以存在元素,因此,要判斷隊列是否為空,需要同時判斷兩個棧。
/** Returns whether the queue is empty. */
public boolean empty() {
return inStack.isEmpty() && outStack.isEmpty();
}
復雜度分析如下:
- 時間復雜度:\(O(1)\)
- 空間復雜度:\(O(1)\)
Java 實現
class MyQueue {
/**
* The stack used to implement enqueue functionality
*/
private Stack<Integer> inStack;
/**
* The stack used to implement dequeue functionality
*/
private Stack<Integer> outStack;
/**
* The front element in the stack `inStack` 's queue
*/
private int front;
/** Initialize your data structure here. */
public MyQueue2() {
inStack = new Stack<>();
outStack = new Stack<>();
}
/** Push element x to the back of queue. */
public void push(int x) {
if (inStack.empty()) {
front = x;
}
inStack.push(x);
}
/** Removes the element from in front of queue and returns that element. */
public int pop() {
if (empty()) {
throw new IllegalArgumentException("[ERROR] The queue is empty!");
}
if (outStack.isEmpty()) {
while (!inStack.isEmpty()) {
outStack.push(inStack.pop());
}
}
return outStack.pop();
}
/** Get the front element. */
public int peek() {
if (empty()) {
throw new IllegalArgumentException("[ERROR] The queue is empty!");
}
if (!outStack.isEmpty()) {
return outStack.peek();
} else {
return front;
}
}
/** Returns whether the queue is empty. */
public boolean empty() {
return inStack.isEmpty() && outStack.isEmpty();
}
}
Python 實現
class MyQueue:
def __init__(self):
"""
Initialize your data structure here.
"""
self._in_stack, self._out_stack, self._front = [], [], None
def push(self, x):
"""
Push element x to the back of queue.
:type x: int
:rtype: void
"""
if not self._in_stack:
self._front = x
self._in_stack.append(x)
def pop(self):
"""
Removes the element from in front of queue and returns that element.
:rtype: int
"""
if self.empty():
raise Exception("[ERROR] The queue is empty!")
if not self._out_stack:
while self._in_stack:
self._out_stack.append(self._in_stack.pop())
return self._out_stack.pop()
def peek(self):
"""
Get the front element.
:rtype: int
"""
if self.empty():
raise Exception("[ERROR] The queue is empty!")
if not self._out_stack:
return self._front
else:
return self._out_stack[-1]
def empty(self):
"""
Returns whether the queue is empty.
:rtype: bool
"""
return not self._in_stack and not self._out_stack
【LeetCode題解】232_用棧實現隊列(Implement-Queue-using-Stacks)