刷題路線參考:
大家好,我是靠寫博客督促自己刷題的老三,這一節我們對線棧和隊列。
棧和隊列基礎
在正式開刷之前,我們先了解一些棧和隊列的基礎知識。
棧的結構
棧是一種先進后出的順序表結構。

棧的結構比較簡單,就不多了。
棧的實現
因為棧是一個線性表表,因此,線性表支持棧的操作,ArrayList 和 LinkedList 都可以作為棧來使用。
可以直接使用 Stack 類來進行創建一個棧,這個繼承的是過期。
Deque<TreeNode> stack = new LinkedList<TreeNode>();//類型為TreeNode
Stack<TreeNode> stack = new Stack<TreeNode>();
隊列結構
隊列是一種先進先出的數據結構

隊列實現
隊列也是表結構,比較常用的是由LinkedList實現。
Queue<TreeNode> queue = new LinkedList<TreeNode>();
好了,兩種數據結構也比較簡單,開始我們的刷題之旅吧!
刷題現場
劍指 Offer 09. 用兩個棧實現隊列
☕ 題目:劍指 Offer 09. 用兩個棧實現隊列(https://leetcode-cn.com/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/)
❓ 難度:簡單
📕 描述:
用兩個棧實現一個隊列。隊列的聲明如下,請實現它的兩個函數 appendTail 和 deleteHead ,分別完成在隊列尾部插入整數和在隊列頭部刪除整數的功能。(若隊列中沒有元素,deleteHead 操作返回 -1 )
示例 1:
輸入:
["CQueue","appendTail","deleteHead","deleteHead"]
[[],[3],[],[]]
輸出:[null,null,3,-1]
示例 2:
輸入:
["CQueue","deleteHead","appendTail","appendTail","deleteHead","deleteHead"]
[[],[],[5],[2],[],[]]
輸出:[null,-1,null,null,5,2]
💡思路:
棧是先進后出,隊列是先進先出的數據結構。
那怎么用棧模擬隊列呢?
需要兩個棧,一個作為隊頭 headStack,一個作為隊尾 tailStack。
- tailStack 作為隊尾,模擬入隊操作,當有一個元素入隊時,則將其 push 到tailStack 棧頂。
- headStack 作為隊頭,模擬出隊操作,當有一個元素出隊時,則將 headStack 棧頂的元素 pop。
- 當 headStack 中沒有元素時,將 tailStack 中所有的元素都 push 進 headStack 中。

這樣一來,就用兩個棧模擬了隊列的出入順序。
我們來看一下代碼實現:
public class CQueue {
//定義兩個棧
Deque<Integer> headStack, tailStack;
public CQueue() {
headStack = new LinkedList<>();
tailStack = new LinkedList<>();
}
//入隊
public void appendTail(int value) {
//入隊,往tailStack中壓入值
tailStack.push(value);
}
//出隊
public int deleteHead() {
//如果隊頭為空
if (headStack.isEmpty()) {
//則將 tailStack (隊尾)的元素全部出棧,再壓入headStack
while (!tailStack.isEmpty()) {
headStack.push(tailStack.pop());
}
}
if (headStack.isEmpty()) {
return -1;
}
return headStack.pop();
}
}
🚗 時間復雜度:入隊O(1,出隊O(n)。
🏠 空間復雜度:引入了兩個棧,所以空間復雜度O(n)。
還有一道題,LeetCode232. 用棧實現隊列 基本是一樣的。
LeetCode225. 用隊列實現棧
☕ 題目:225. 用隊列實現棧 (https://leetcode-cn.com/problems/implement-stack-using-queues/)
❓ 難度:簡單
📕 描述:
請你僅使用兩個隊列實現一個后入先出(LIFO)的棧,並支持普通棧的全部四種操作(push、top、pop 和 empty)。
實現 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(雙端隊列)來模擬一個隊列 , 只要是標准的隊列操作即可。
示例:
輸入:
["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
💡 思路:
這道題,實不相瞞,乍一看,我有點想偷懶,因為如果用一個雙向隊列的話:

是不是啪一下就實現了,但是題目里面也說了,標准隊列操作,所以我們還是用單向隊列。
那我們怎么實現呢?
很簡單,入棧的時候,我們利用隊列先進先出的特點,每次隊列模擬入棧時,我們先將隊列之前入隊的元素都出隊,僅保留最后一個進隊的元素。
然后再重新入隊,這樣就實現了顛倒隊列中的元素,這樣就和棧中的次序是一樣的了。

代碼實現如下:
public class MyStack {
//單向隊列
Queue<Integer> queue;
/**
* Initialize your data structure here.
*/
public MyStack() {
queue = new LinkedList<>();
}
/**
* Push element x onto stack.
*/
public void push(int x) {
//入隊元素
queue.offer(x);
//將之前的元素,出隊,重新入隊
for (int i = 0; i < queue.size() - 1; i++) {
queue.offer(queue.poll());
}
}
/**
* Removes the element on top of the stack and returns that element.
*/
public int pop() {
return queue.poll();
}
/**
* Get the top element.
*/
public int top() {
return queue.peek();
}
/**
* Returns whether the stack is empty.
*/
public boolean empty() {
return queue.isEmpty();
}
}
🚗 時間復雜度:入棧O(n),出棧O(1)。
🏠 空間復雜度:引入了隊列,空間復雜度O(n)。
LeetCode20. 有效的括號
☕ 題目:20. 有效的括號 (https://leetcode-cn.com/problems/valid-parentheses/)
❓ 難度:簡單
📕 描述:
給定一個只包括 '(',')','{','}','[',']' 的字符串 s ,判斷字符串是否有效。
有效字符串需滿足:
- 左括號必須用相同類型的右括號閉合。
- 左括號必須以正確的順序閉合。
示例 1:
輸入:s = "()"
輸出:true
示例 2:
輸入:s = "()[]{}"
輸出:true
示例 3:
輸入:s = "(]"
輸出:false
示例 4:
輸入:s = "([)]"
輸出:false
示例 5:
輸入:s = "{[]}"
輸出:true
提示:
1 <= s.length <= 104s僅由括號'()[]{}'組成
💡 思路:
這是一道經典的棧的應用的題目。
思路是什么呢?
碰到左括號把元素入棧,碰到右括號就和棧頂元素比較,如果相同就把棧頂元素出棧,不匹配,就直接返回false。

代碼如下:注意處理棧為空的情況
public boolean isValid(String s) {
Deque<Character> stack = new LinkedList<>();
//遍歷字符串
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
//遇到左括號,入棧
if (c == '(' || c == '[' || c == '{') {
stack.push(c);
}
//右括號匹配
if (c == ')') {
if (stack.isEmpty() || stack.pop() != '(') {
return false;
}
}
if (c == ']') {
if (stack.isEmpty() || stack.pop() != '[') {
return false;
}
}
if (c == '}') {
if (stack.isEmpty() || stack.pop() != '{') {
return false;
}
}
}
return stack.isEmpty();
}
🚗 時間復雜度:O(n)。
🏠 空間復雜度:O(n)。
LeetCode1047. 刪除字符串中的所有相鄰重復項
給出由小寫字母組成的字符串 S,重復項刪除操作會選擇兩個相鄰且相同的字母,並刪除它們。
在 S 上反復執行重復項刪除操作,直到無法繼續刪除。
在完成所有重復項刪除操作后返回最終的字符串。答案保證唯一。
示例:
輸入:"abbaca"
輸出:"ca"
解釋:
例如,在 "abbaca" 中,我們可以刪除 "bb" 由於兩字母相鄰且相同,這是此時唯一可以執行刪除操作的重復項。之后我們得到字符串 "aaca",其中又只有 "aa" 可以執行重復項刪除操作,所以最后的字符串為 "ca"。
💡 思路:
這道題是不是和上道題差不多。
遍歷字符,如果字符和棧頂元素匹配,就把棧頂元素出棧。
如果不匹配,就把元素入棧。
這樣一來,棧里最后剩下的都是相鄰不相同的元素。

代碼如下:最后出棧的元素需要倒轉
public String removeDuplicates(String s) {
Deque<Character> stack = new LinkedList<>();
//遍歷字符串
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (stack.isEmpty() || stack.peek() != c) {
//入棧
stack.push(c);
} else {
//棧頂元素出棧
stack.pop();
}
}
//拼接棧中字符
StringBuilder str = new StringBuilder();
while (!stack.isEmpty()) {
str.append(stack.pop());
}
return str.reverse().toString();
}
🚗 時間復雜度:O(n)。
🏠 空間復雜度:O(n)。
LeetCode150. 逆波蘭表達式求值
☕ 題目:150. 逆波蘭表達式求值 (https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/)
❓ 難度:中等
📕 描述:
根據 逆波蘭表示法,求表達式的值。
有效的算符包括 +、-、*、/ 。每個運算對象可以是整數,也可以是另一個逆波蘭表達式。
說明:
-
整數除法只保留整數部分。
-
給定逆波蘭表達式總是有效的。換句話說,表達式總會得出有效數值且不存在除數為 0 的情況。
示例 1:
輸入:tokens = ["2","1","+","3","*"]
輸出:9
解釋:該算式轉化為常見的中綴算術表達式為:((2 + 1) * 3) = 9
示例 2:
輸入:tokens = ["4","13","5","/","+"]
輸出:6
解釋:該算式轉化為常見的中綴算術表達式為:(4 + (13 / 5)) = 6
示例 3:
輸入:tokens = ["10","6","9","3","+","-11","*","/","*","17","+","5","+"]
輸出:22
解釋:
該算式轉化為常見的中綴算術表達式為:
((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22
逆波蘭表達式:
逆波蘭表達式是一種后綴表達式,所謂后綴就是指算符寫在后面。
- 平常使用的算式則是一種中綴表達式,如 ( 1 + 2 ) * ( 3 + 4 ) 。
- 該算式的逆波蘭表達式寫法為 ( ( 1 2 + ) ( 3 4 + ) * ) 。
逆波蘭表達式主要有以下兩個優點:
- 去掉括號后表達式無歧義,上式即便寫成 1 2 + 3 4 + * 也可以依據次序計算出正確結果。
- 適合用棧操作運算:遇到數字則入棧;遇到算符則取出棧頂兩個數字進行計算,並將結果壓入棧中。
💡 思路:
看到沒,這道題的提示里面就給出了思路:
適合用棧操作運算:遇到數字則入棧;遇到算符則取出棧頂兩個數字進行計算,並將結果壓入棧中。

代碼實現如下:
public int evalRPN(String[] tokens) {
Deque<Integer> stack = new LinkedList<>();
for (int i = 0; i < tokens.length; i++) {
String s = tokens[i];
if (isNumber(s)) {
stack.push(Integer.parseInt(s));
} else {
int num1 = stack.pop();
int num2 = stack.pop();
if (s.equals("+")) {
stack.push(num1 + num2);
} else if (s.equals("-")) {
stack.push(num2 - num1);
} else if (s.equals("*")) {
stack.push(num1 * num2);
} else if (s.equals("/")) {
stack.push(num2 / num1);
}
}
}
return stack.pop();
}
boolean isNumber(String token) {
return !("+".equals(token) || "-".equals(token) || "*".equals(token) || "/".equals(token));
}
🚗 時間復雜度:O(n)。
🏠 空間復雜度:O(n)。
LeetCode402. 移掉 K 位數字
☕ 題目:150. 逆波蘭表達式求值 (https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/)
❓ 難度:中等
📕 描述:
給你一個以字符串表示的非負整數 num 和一個整數 k ,移除這個數中的 k 位數字,使得剩下的數字最小。請你以字符串形式返回這個最小的數字。
示例 1 :
輸入:num = "1432219", k = 3
輸出:"1219"
解釋:移除掉三個數字 4, 3, 和 2 形成一個新的最小的數字 1219 。
示例 2 :
輸入:num = "10200", k = 1
輸出:"200"
解釋:移掉首位的 1 剩下的數字為 200. 注意輸出不能有任何前導零。
示例 3 :
輸入:num = "10", k = 2
輸出:"0"
解釋:從原數字移除所有的數字,剩余為空就是 0 。
💡 思路:
-
遍歷字符串 s,若當前棧不為空並且棧頂元素的值大於當前遍歷的元素值並且移除元素的個數沒有達到要求 k,則棧頂元素出棧,count 值加 1。
-
若當前遍歷的元素比棧頂元素的值要大,則直接將該元素壓棧。
-
若當前遍歷的元素值為 " 0 " 並且棧為空,則直接跳過這次循環(要保證棧底的元素不能為 " 0 ",因為題目要求 num 不會包含任何前導零,就是不能用 " 0 " 來開頭)。
-
若遍歷完整個字符串而 count < k(移除的元素個數沒有達到要求,示例:num = "123456", k = 3),此時直接將棧中的前三個元素依次出棧,即 " 654 " 出棧剩下的 " 321 " 翻轉一下,即為最小值。
-
若當前棧為空(去掉一個最大的元素后,其余元素均為 " 0 "),則直接返回 " 0 " 即可。
代碼如下:
public String removeKdigits(String num, int k) {
if (k == num.length()) return "0";
//棧
Deque<Character> stack = new LinkedList<>();
int count = 0;
for (int i = 0; i < num.length(); i++) {
while (!stack.isEmpty() && stack.peek() > num.charAt(i) && count < k) {
stack.pop();
count++;
}
//棧為空,且當前位為0時,我們不需要將其入棧
if (num.charAt(i) == '0' && stack.isEmpty()) continue;
stack.push(num.charAt(i));
}
while (!stack.isEmpty() && count < k) {
stack.pop();
count++;
}
//這個是最后棧為空時,刪除一位,比如我們的10,刪除一位為0,
//按上面邏輯我們會返回"",所以我們讓其返回"0"
if (stack.isEmpty()) return "0";
StringBuffer sb = new StringBuffer();
while (!stack.isEmpty()) {
sb.append(stack.pop());
}
return sb.reverse().toString();
}
總結
大家熟悉😂的順口溜總結:

簡單的事情重復做,重復的事情認證做,認證的事情有創造性地做。——
我是三分惡,一個能文能武的全棧開發。
點贊、關注不迷路,咱們下期見。
參考:
[1]. https://github.com/youngyangyang04/leetcode-master
[2]. https://github.com/chefyuan/algorithm-base
[3]. https://leetcode-cn.com/problems/remove-k-digits/solution/yi-diao-kwei-shu-zi-zhan-by-booooo_-01nh/
