Stack — 實戰題解

雜湊、堆疊與佇列優先度 4/5 — 高價值 — 這裡有缺口就會掉關高價值 更新於 Nov 12, 2026
章節優先度優先度 5/5 — 必備 — 幾乎每一輪面試都會出現必備優先度 4/5 — 高價值 — 這裡有缺口就會掉關高價值優先度 3/5 — 值得會 — 多半是必備模式的變形值得會優先度 2/5 — 冷門 — 讀過一次即可,除非目標公司已知會問冷門只標在真正需要的章節上 —— 沒標的是背景/參考資料。

範圍 — stack.md 背後的題庫:不是任何模板錨點題的堆疊題,各自以題目卡的形式放在它所歸約到的模板底下;單調堆疊題在 monotonic_stack.md 裡完整解出,這裡只放指向它們的連結。 另見:stack.md — 母頁:這些題目所歸約到的六份模板;monotonic_stack.md — next greater/previous smaller、span、直方圖與貪婪移除,完整解在那裡;stack_expression_parsing.md — 計算機、decode string 與後綴運算求值,那是自成一家的題型;iterator.md — 以堆疊為底的迭代器(LC 173、LC 341);queue.md — FIFO 那一側,包括 LC 232。

LeetCode 題目清單

總覽

stack.md 放六份模板;這份表是套用它們的題目。底下每個 ## 小節對應一份模板,順序和核心表相同,每張卡片都寫出該題在模板之上多加的那一步。已經在別份表裡解出的題目,在索引(以及所屬小節的指標表)裡只佔一列,不另抄一份。

索引

LC 題目 歸約到 難度 在哪裡
1047 Remove All Adjacent Duplicates In String stack § Template 1 Easy 錨點題,完整解在 stack.md
844 Backspace String Compare advanced_simulation § Template 6 Easy 完整解在 advanced_simulation.md
946 Validate Stack Sequences advanced_simulation § Template 6 Medium 完整解在 advanced_simulation.md
1910 Remove All Occurrences of a Substring advanced_simulation § Template 6 Medium 完整解在 advanced_simulation.md
1209 Remove All Adjacent Duplicates in String II stack § Template 1 Medium 卡片
735 Asteroid Collision stack § Template 1 Medium 卡片
20 Valid Parentheses stack § Template 2 Easy 錨點題,完整解在 stack.md
921 Minimum Add to Make Parentheses Valid greedy_examples § 7 Medium 完整解在 greedy_examples.md
1249 Minimum Remove to Make Valid Parentheses string § Template 7 Medium 索引堆疊在 string.md;兩趟掃描卡片 在這裡
856 Score of Parentheses stack § Template 2 Medium 卡片
1963 Minimum Number of Swaps to Make the String Balanced stack § Template 2 Medium 卡片
32 Longest Valid Parentheses stack § Template 2 Hard 卡片
496 Next Greater Element I monotonic_stack § 2-3 Easy 完整解在 monotonic_stack.md
739 Daily Temperatures stack § Template 3 Medium 錨點題,完整解在 stack.md
503 Next Greater Element II monotonic_stack § 2-5 Medium 完整解在 monotonic_stack.md
901 Online Stock Span monotonic_stack § 2-6 Medium 完整解在 monotonic_stack.md
907 Sum of Subarray Minimums monotonic_stack § 2-7 Medium 完整解在 monotonic_stack.md
2104 Sum of Subarray Ranges monotonic_stack § 2-12 Medium 完整解在 monotonic_stack.md
402 Remove K Digits monotonic_stack § 2-8 Medium 完整解在 monotonic_stack.md
316 Remove Duplicate Letters monotonic_stack § 2-8-1 Medium 完整解在 monotonic_stack.md
84 Largest Rectangle in Histogram monotonic_stack § 2-2 Hard 完整解在 monotonic_stack.md
155 Min Stack stack § Template 4 Medium 錨點題,完整解在 stack.md
144 Binary Tree Preorder Traversal stack § Template 5 Easy 錨點題,完整解在 stack.md
145 Binary Tree Postorder Traversal stack § Template 5 Easy 錨點題,完整解在 stack.md
232 Implement Queue using Stacks queue § Template 5 Easy 完整解在 queue.md
173 Binary Search Tree Iterator bst_advanced § Template 5b Medium 完整解在 bst_advanced.md
341 Flatten Nested List Iterator iterator § 2-2 Medium 完整解在 iterator.md
445 Add Two Numbers II stack § Template 5 Medium 卡片
71 Simplify Path string § Variation 6.1 Medium 完整解在 string.md
388 Longest Absolute File Path stack § Template 6 Medium 錨點題,完整解在 stack.md
636 Exclusive Time of Functions stack § Template 6 Medium 錨點題,完整解在 stack.md

與頂端抵銷的題目

LC 題目 完整解在哪裡
1047 Remove All Adjacent Duplicates In String stack § 模板 1 — 錨點題
844 · 946 · 1910 Backspace String Compare · Validate Stack Sequences · Remove All Occurrences of a Substring advanced_simulation § 模板 6

Remove All Adjacent Duplicates in String II — LC 1209 優先度 4/5 — 高價值 — 這裡有缺口就會掉關

關鍵 — 堆疊裡每一筆是一整段連續段 [char, length],所以堆疊就是化簡後前綴的遊程編碼。相同字元把頂端那段加長;一旦長度達到 k,整段就被 pop 掉,下一段露出來接受下一個字元 —— 這就是不用第二趟掃描也能連鎖消除的原因。

歸約到:stack § 模板 1 — 抵銷需要 k 個相鄰相同字元而不是兩個,所以頂端要帶一個計數。LC 1047 就是 k = 2。

python
# python
# LC 1209 - Remove All Adjacent Duplicates in String II
# IDEA: stack of [char, run length]; a run that reaches k is popped whole
# time = O(n), space = O(n)
class Solution(object):
    def removeDuplicates(self, s, k):
        stack = []                       # [char, run length] — the reduced prefix, run-length encoded
        for ch in s:
            if stack and stack[-1][0] == ch:
                stack[-1][1] += 1
                if stack[-1][1] == k:
                    stack.pop()          # the run reached k: it vanishes, exposing the run below
            else:
                stack.append([ch, 1])
        return ''.join(ch * cnt for ch, cnt in stack)
java
// java
// LC 1209 - Remove All Adjacent Duplicates in String II
// IDEA: stack of {char, run length}; a run that reaches k is popped whole
// time = O(n), space = O(n)
public String removeDuplicates(String s, int k) {
    Deque<int[]> stack = new ArrayDeque<>();      // {char, run length}, top = last run
    for (char ch : s.toCharArray()) {
        if (!stack.isEmpty() && stack.peek()[0] == ch) {
            stack.peek()[1]++;
            if (stack.peek()[1] == k) {
                stack.pop();                      // the run vanishes, exposing the run below
            }
        } else {
            stack.push(new int[]{ch, 1});
        }
    }
    StringBuilder sb = new StringBuilder();
    while (!stack.isEmpty()) {
        int[] run = stack.pollLast();             // the bottom is the leftmost run
        for (int i = 0; i < run[1]; i++) {
            sb.append((char) run[0]);
        }
    }
    return sb.toString();
}

試跑

text
s = "deeedbbcccbdaa", k = 3
ch   stack (runs)                     note
d    [d1]
e    [d1 e1]
e    [d1 e2]
e    [d1]                             e reaches 3 -> popped
d    [d2]                             the pop exposed d, so this d extends it
b    [d2 b1]
b    [d2 b2]
c    [d2 b2 c1]
c    [d2 b2 c2]
c    [d2 b2]                          c reaches 3 -> popped
b    [d2]                             b reaches 3 -> popped (cascade)
d    []                               d reaches 3 -> popped (cascade)
a    [a1]
a    [a2]                             -> "aa"

陷阱

  • 只 push 單一字元、每一步都重數頂端 k 個,是 O(n·k);k = 10⁴、n = 10⁵ 時會超時。把計數存在記錄上。
  • 計數歸零而不是 pop("deeed",k = 3)會在頂端留下一段空的連續段,第二個 d 就另起一段,連鎖消成 "dd" 永遠不會發生。
  • Java:用 for-each 走訪 ArrayDeque 是頂端 → 底部,答案會反過來;用 pollLast() 取出。

相似題 — LC 1047(k = 2,不需要計數)· LC 1544(同一個字母、大小寫相反時抵銷)

Asteroid Collision — LC 735

關鍵 — 堆疊裡放的是到目前為止的倖存者,而碰撞只會發生在往左飛的新來者(a < 0)和往右飛的頂端(top > 0)之間。新來者在 while 迴圈裡不斷擊毀比較小的頂端,所以一顆小行星可以 pop 掉很多顆;迴圈停下時它還活著才 push。

歸約到:stack § 模板 1 — 抵銷的判斷是比大小,而且雙方都可能被毀,不只頂端。

python
# python
# LC 735 - Asteroid Collision
# IDEA: stack of survivors; a left-mover fights right-moving tops until one side is gone
# time = O(n), space = O(n)
class Solution(object):
    def asteroidCollision(self, asteroids):
        stack = []                       # survivors, left to right
        for a in asteroids:
            alive = True
            # a collision needs the newcomer moving left and the top moving right
            while alive and a < 0 and stack and stack[-1] > 0:
                if stack[-1] < -a:
                    stack.pop()          # the top explodes; `a` keeps flying left
                    continue
                if stack[-1] == -a:
                    stack.pop()          # equal size: both explode
                alive = False            # `a` explodes (or already did with the top)
            if alive:
                stack.append(a)
        return stack
java
// java
// LC 735 - Asteroid Collision
// IDEA: stack of survivors; a left-mover fights right-moving tops until one side is gone
// time = O(n), space = O(n)
public int[] asteroidCollision(int[] asteroids) {
    Deque<Integer> stack = new ArrayDeque<>();
    for (int a : asteroids) {
        boolean alive = true;
        while (alive && a < 0 && !stack.isEmpty() && stack.peek() > 0) {
            if (stack.peek() < -a) {
                stack.pop();                  // the top explodes, `a` keeps flying
                continue;
            }
            if (stack.peek() == -a) {
                stack.pop();                  // both explode
            }
            alive = false;                    // `a` explodes
        }
        if (alive) {
            stack.push(a);
        }
    }
    int[] res = new int[stack.size()];
    for (int i = res.length - 1; i >= 0; i--) {
        res[i] = stack.pop();                 // pop fills from the right end
    }
    return res;
}

陷阱

  • 只要正負號不同就相撞:[-2, 1] 是互相遠離(左邊往左、右邊往右),必須維持 [-2, 1]。只有頂端往右、新來者往左才會相遇。
  • 大小相等時兩顆都毀掉:[8, -8] 是 [],不是 [8] 也不是 [-8]。
  • 只打一場就把新來者 push:[10, 2, -5] 必須讓 -5 先擊毀 2,再撞上 10 而毀掉,得到 [10]。碰撞是 while,不是 if。

括號配對的題目

LC 題目 完整解在哪裡
20 Valid Parentheses stack § 模板 2 — 錨點題
921 Minimum Add to Make Parentheses Valid greedy_examples § 7 — 只有一種括號,所以堆疊退化成計數器
1249 Minimum Remove to Make Valid Parentheses(索引堆疊) string § 模板 7 — 不用堆疊的寫法是下面這張卡片

Minimum Remove to Make Valid Parentheses — LC 1249

關鍵 — 索引堆疊的解法在 string § 模板 7; 這張是不用堆疊的版本。只有一種括號時,堆疊可以縮成一個計數器,但計數器記不住多出來的 ( 在哪裡,所以按方向拆開:左 → 右刪掉每個前面沒有開著的 ( 的 ),再右 → 左刪掉每個後面沒有開著的 ) 的 (。每一趟只刪不可能配對的括號,所以刪得最少。

歸約到:stack § 模板 2 — 堆疊退化成它的大小,每個方向各跑一次。它回答的是 follow-up 「不用索引堆疊做得到嗎?」,而且和下面 LC 32 的計數掃描是同一個雙向技巧。

python
# python
# LC 1249 - Minimum Remove to Make Valid Parentheses
# IDEA: 2 SCANS with a balance counter — drop unmatched ')' left->right, unmatched '(' right->left
# time = O(n), space = O(n) for the two output buffers; O(1) beyond them
class Solution(object):
    def minRemoveToMakeValid(self, s):
        # pass 1: left -> right; a ')' with no '(' open before it can never be matched
        kept = []
        open_cnt = 0
        for c in s:
            if c == ')':
                if open_cnt == 0:
                    continue        # unmatched ')' -> drop
                open_cnt -= 1
            elif c == '(':
                open_cnt += 1
            kept.append(c)

        # pass 2: right -> left, the mirror; a '(' with no ')' open after it can never be matched
        res = []
        close_cnt = 0
        for c in reversed(kept):
            if c == '(':
                if close_cnt == 0:
                    continue        # unmatched '(' -> drop
                close_cnt -= 1
            elif c == ')':
                close_cnt += 1
            res.append(c)

        res.reverse()               # pass 2 built it backwards
        return ''.join(res)
java
// java
// LC 1249 - Minimum Remove to Make Valid Parentheses
// IDEA: 2 SCANS with a balance counter — drop unmatched ')' left->right, unmatched '(' right->left
// time = O(n), space = O(n) for the two output buffers; O(1) beyond them
public String minRemoveToMakeValid(String s) {
    StringBuilder kept = new StringBuilder();
    int openCnt = 0;
    for (char c : s.toCharArray()) {
        if (c == ')') {
            if (openCnt == 0) {
                continue;      // unmatched ')' -> drop
            }
            openCnt--;
        } else if (c == '(') {
            openCnt++;
        }
        kept.append(c);
    }
    StringBuilder res = new StringBuilder();
    int closeCnt = 0;
    for (int i = kept.length() - 1; i >= 0; i--) {
        char c = kept.charAt(i);
        if (c == '(') {
            if (closeCnt == 0) {
                continue;      // unmatched '(' -> drop
            }
            closeCnt--;
        } else if (c == ')') {
            closeCnt++;
        }
        res.append(c);
    }
    return res.reverse().toString(); // pass 2 built it backwards
}

試跑

text
s = ")((a)"

pass 1, left -> right   )  open=0 -> drop       (  open=1   (  open=2   a   )  open=1
                        kept = "((a)"
pass 2, right -> left   )  close=1   a   (  close=0   (  close=0 -> drop
                        res  = "(a)"

陷阱

  • 只跑一趟左 → 右:"(()" 會留下多出來的 (,因為 open_cnt 最後只是停在 1。反方向的第二趟才會把它刪掉。
  • 第二趟之後忘了反轉:")((a)" 會回傳 ")a("。

Score of Parentheses — LC 856

關鍵 — 每個堆疊槽位存的是這一層目前收集到的分數。( 開一個新框(push 0);) 關掉它並併進父層:空框代表 "()",得 1 分,非空的框加倍,所以父層加上 max(2 * inner, 1)。

歸約到:stack § 模板 2 — 堆疊為每個開著的深度存一份部分結果,而不是左括號本身。

python
# python
# LC 856 - Score of Parentheses
# IDEA: STACK OF PARTIAL SCORES — one frame per depth, fold child into parent
# time = O(n), space = O(n)
class Solution(object):
    def scoreOfParentheses(self, s):
        stack = [0]                  # stack[-1] = score collected so far at the current depth
        for c in s:
            if c == '(':
                stack.append(0)      # open a frame for the new depth
            else:
                inner = stack.pop()  # close it: "()" scores 1, "(X)" scores 2 * X
                stack[-1] += max(2 * inner, 1)
        return stack[0]
java
// java
// LC 856 - Score of Parentheses
// IDEA: STACK OF PARTIAL SCORES — one frame per depth, fold child into parent
// time = O(n), space = O(n)
public int scoreOfParentheses(String s) {
    Deque<Integer> st = new ArrayDeque<>();
    st.push(0); // score of the outermost frame
    for (char c : s.toCharArray()) {
        if (c == '(') {
            st.push(0); // open a new (empty) frame
        } else {
            int inner = st.pop();
            st.push(st.pop() + Math.max(2 * inner, 1)); // "()" scores 1, "(X)" scores 2 * X
        }
    }
    return st.pop();
}

陷阱

  • 沒有最外層的 0 框:"()()" 在第一個 ) 就把堆疊 pop 空,分數沒地方加。堆疊要先放 [0]。
  • 只寫 2 * inner 會把 "()" 算成 0;max(..., 1) 就是 base case。

Minimum Number of Swaps to Make the String Balanced — LC 1963

關鍵 — 用堆疊把每一對配好的 [] 抵銷掉,剩下的永遠是 "]" * m + "[" * m:每個沒配對的 ] 都排在每個沒配對的 [ 前面。把最外側的 ] 和最外側的 [ 交換一次,能修好那 m 對中的兩對,所以答案是 ceil(m / 2)。

歸約到:stack § 模板 2 — 當作化簡器用:掃描後只剩不平衡的核心,答案是它大小的一個公式。

python
# python
# LC 1963 - Minimum Number of Swaps to Make the String Balanced
# IDEA: cancel matched pairs; the residue is "]"*m + "["*m and one swap fixes two pairs
# time = O(n), space = O(n)
class Solution(object):
    def minSwaps(self, s):
        stack = []
        for c in s:
            if c == ']' and stack and stack[-1] == '[':
                stack.pop()              # a matched pair cancels
            else:
                stack.append(c)          # an unmatched ']' or a '[' still open
        # what is left is "]" * m + "[" * m: every ']' sits before every '['
        m = len(stack) // 2
        return (m + 1) // 2              # one swap repairs two of the m pairs

陷阱

  • 每遇到 ] 就 pop、不檢查頂端是不是 [:在 "]]][[[" 上兩個 ] 會互相抵銷,剩下 "][[[" 而不是 "]]][[["。pop 之前先確認頂端配得上。
  • 用 m // 2 而不是 (m + 1) // 2:"][][" 的 m = 1,需要交換一次,不是零次。
  • 堆疊裡永遠是 ] 在下、[ 在上,所以只數未配對的 ] 也能得到同一個 m,空間 O(1)。

Longest Valid Parentheses — LC 32 優先度 4/5 — 高價值 — 這裡有缺口就會掉關

關鍵 — push 索引,並讓堆疊最底下那個元素是目前合法區段前一格的索引(基準,初始放 -1)。遇到 ) 先 pop:如果堆疊空了,這個 ) 沒有配對,成為新的基準;否則 i - stack[-1] 就是以 i 結尾的合法區段長度。

歸約到:stack § 模板 2 — 存索引而不是字元,再加一個把「配對成功」變成「長度」的基準哨兵。

python
# python
# LC 32 - Longest Valid Parentheses
# IDEA: STACK OF INDICES + `-1` base sentinel; length = i - stack[-1]
# time = O(n), space = O(n)
class Solution(object):
    def longestValidParentheses(self, s):
        stack = [-1]         # bottom = index just before the current valid run (the base)
        best = 0
        for i, c in enumerate(s):
            if c == '(':
                stack.append(i)
            else:
                stack.pop()                       # match the top '(' — or pop the base
                if not stack:
                    stack.append(i)               # unmatched ')' -> it becomes the new base
                else:
                    best = max(best, i - stack[-1])
        return best
java
// java
// LC 32 - Longest Valid Parentheses
// IDEA: STACK OF INDICES + `-1` base sentinel; length = i - stack.peek()
// time = O(n), space = O(n)
public int longestValidParentheses(String s) {
    Deque<Integer> st = new ArrayDeque<>();
    st.push(-1);                    // base: the index just before the current valid run
    int best = 0;
    for (int i = 0; i < s.length(); i++) {
        if (s.charAt(i) == '(') {
            st.push(i);
        } else {
            st.pop();               // match the top '(' — or pop the base
            if (st.isEmpty()) {
                st.push(i);         // unmatched ')' -> the new base
            } else {
                best = Math.max(best, i - st.peek());
            }
        }
    }
    return best;
}

試跑

text
s = ")()())"

i  c   action                       stack        best
-  -   init base                    [-1]         0
0  )   pop -1 -> empty -> new base  [0]          0
1  (   push                         [0, 1]       0
2  )   pop 1, i - top = 2 - 0 = 2   [0]          2
3  (   push                         [0, 3]       2
4  )   pop 3, i - top = 4 - 0 = 4   [0]          4  <- answer
5  )   pop 0 -> empty -> new base   [5]          4

另外兩個值得知道的解法。 堆疊是要先寫的那個;這兩個各自教了堆疊沒教的東西。

做法 時間 空間 它教你什麼
索引堆疊 + -1 基準(上面) O(n) O(n) 基準哨兵把一次配對變成一個長度
1D DP — dp[i] = 以 i 結尾的最長合法區段 O(n) O(n) 跳過以 i-1 結尾的區段,找到 s[i] 的另一半
兩趟計數掃描(左→右,再右→左) O(n) O(1) 一個計數器看不見多出來的 (,所以要倒著再掃一次

1D DP。 合法區段只能以 ) 結尾,所以 s[i] == '(' 時 dp[i] = 0。遇到 ):如果 s[i-1] == '(',就是一對新括號黏在前一段後面,dp[i] = dp[i-2] + 2;否則以 i-1 結尾的區段長 dp[i-1],所以 s[i] 的另一半只可能在 open_idx = i - dp[i-1] - 1,如果那裡是 (,則 dp[i] = dp[i-1] + 2 + dp[open_idx-1] —— 最後一項把另一半之前剛結束的那段也黏上來,這是大家最常忘的部分。

python
# python
# LC 32 - Longest Valid Parentheses
# IDEA: 1D DP — dp[i] = longest valid substring ENDING at i
# time = O(n), space = O(n)
class Solution(object):
    def longestValidParentheses(self, s):
        n = len(s)
        dp = [0] * n                      # dp[i] = longest valid run ENDING at i
        best = 0
        for i in range(1, n):
            if s[i] == '(':
                continue                  # a valid run never ends with '('
            if s[i - 1] == '(':
                # case 1: "...()" -> the new pair + the run ending at i-2
                dp[i] = 2 + (dp[i - 2] if i >= 2 else 0)
            else:
                # case 2: "...))" -> jump over the run ending at i-1 to s[i]'s partner
                open_idx = i - dp[i - 1] - 1
                if open_idx >= 0 and s[open_idx] == '(':
                    dp[i] = dp[i - 1] + 2
                    if open_idx >= 1:
                        dp[i] += dp[open_idx - 1]   # glue on the run that ends before the partner
            best = max(best, dp[i])
        return best
text
s = "()(())"

i  c   case                                             dp
0  (   '(' ends nothing                                 [0,0,0,0,0,0]
1  )   "()": 2 + dp[-1 -> 0]                            [0,2,0,0,0,0]
2  (   -                                                [0,2,0,0,0,0]
3  (   -                                                [0,2,0,0,0,0]
4  )   "()": 2 + dp[2] = 2                              [0,2,0,0,2,0]
5  )   "))": open_idx = 5 - dp[4] - 1 = 2, s[2] = '('
           dp[5] = dp[4] + 2 + dp[1] = 2 + 2 + 2 = 6    [0,2,0,0,2,6]  <- answer

兩趟計數掃描。 左 → 右數 opens 和 closes:opens == closes 時結束一段長度 2 * closes 的合法區段,closes > opens 代表這個 ) 永遠配不到,兩個都歸零。左 → 右在 "(()" 上永遠看不到 opens == closes,所以再跑一趟右 → 左的鏡像,在 opens > closes 時歸零。每一段合法區段至少會被其中一趟抓到;這是唯一 O(1) 空間的解。

python
# python
# LC 32 - Longest Valid Parentheses
# IDEA: 2 SCANS with open/close counters — left->right, then right->left
# time = O(n), space = O(1)
class Solution(object):
    def longestValidParentheses(self, s):
        best = 0
        # pass 1: left -> right; a surplus ')' can never be matched -> reset
        opens = closes = 0
        for c in s:
            if c == '(':
                opens += 1
            else:
                closes += 1
            if opens == closes:
                best = max(best, 2 * closes)
            elif closes > opens:
                opens = closes = 0
        # pass 2: right -> left; catches "(()", where opens stays ahead in pass 1
        opens = closes = 0
        for c in reversed(s):
            if c == '(':
                opens += 1
            else:
                closes += 1
            if opens == closes:
                best = max(best, 2 * opens)
            elif opens > closes:
                opens = closes = 0
        return best

陷阱

  • 沒有放 -1:每段從索引 0 開始的區段("()")都會把堆疊 pop 空,永遠量不到。
  • push 字元而不是索引:整個技巧就是 i - stack[-1],所以堆疊必須存位置。
  • DP:漏了 dp[open_idx - 1] 那一項,會把 "()(())" 算成 4 而不是 6。
  • 計數掃描:只掃一個方向在 "(()" 上會回傳 0;鏡像的那一趟是必要的。

單調堆疊的題目

單調堆疊題在 monotonic_stack.md 裡完整解出;核心形式是 stack § 模板 3。

LC 題目 完整解在哪裡
496 Next Greater Element I monotonic_stack § 2-3
739 Daily Temperatures stack § Template 3 — 錨點題
503 Next Greater Element II monotonic_stack § 2-5
901 Online Stock Span monotonic_stack § 2-6
907 Sum of Subarray Minimums monotonic_stack § 2-7
2104 Sum of Subarray Ranges monotonic_stack § 2-12
402 Remove K Digits monotonic_stack § 2-8
316 Remove Duplicate Letters monotonic_stack § 2-8-1
84 Largest Rectangle in Histogram monotonic_stack § 2-2

Min Stack 的題目

LC 題目 完整解在哪裡
155 Min Stack stack § 模板 4 — 錨點題,附只存最小值的省空間第二個堆疊

顯式堆疊的題目

LC 題目 完整解在哪裡
144 · 145 Binary Tree Preorder / Postorder Traversal stack § 模板 5 — 錨點題
232 Implement Queue using Stacks queue § 模板 5
173 Binary Search Tree Iterator bst_advanced § 模板 5b — 暫停式的中序走訪
341 Flatten Nested List Iterator iterator § 2-2 — 子元素倒序 push,在 hasNext() 裡展開

Add Two Numbers II — LC 445

關鍵 — 單向鏈結串列只能往前走,但加法要從最低位開始。把每個節點的值 push 進堆疊,就能不修改輸入地拿到倒序;再用「往前插入」建出答案,等於反轉第二次,所以結果是最高位在前。

歸約到:stack § 模板 5 — 堆疊存還沒做的工作,並以相反順序交回來,這裡是兩條串列的數字。

python
# python
# LC 445 - Add Two Numbers II
# IDEA: TWO STACKS give least-significant-first access without mutating the input
# time = O(n + m), space = O(n + m)
class Solution(object):
    def addTwoNumbers(self, l1, l2):
        s1, s2 = [], []
        while l1:                         # push every digit: popping now yields least significant first
            s1.append(l1.val)
            l1 = l1.next
        while l2:
            s2.append(l2.val)
            l2 = l2.next

        carry = 0
        head = None                       # the answer is built from its tail forwards
        while s1 or s2 or carry:          # a pending carry adds one more digit
            total = carry
            if s1:
                total += s1.pop()
            if s2:
                total += s2.pop()
            carry = total // 10
            node = ListNode(total % 10)
            node.next = head              # prepend: the second reversal puts the digits back in order
            head = node
        return head
java
// java
// LC 445 - Add Two Numbers II
// IDEA: TWO STACKS give least-significant-first access without mutating the input
// time = O(n + m), space = O(n + m)
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
    Deque<Integer> s1 = new ArrayDeque<>();
    Deque<Integer> s2 = new ArrayDeque<>();
    for (ListNode p = l1; p != null; p = p.next) {
        s1.push(p.val);
    }
    for (ListNode p = l2; p != null; p = p.next) {
        s2.push(p.val);
    }
    int carry = 0;
    ListNode head = null;                     // built from the tail forwards
    while (!s1.isEmpty() || !s2.isEmpty() || carry != 0) {
        int sum = carry;
        if (!s1.isEmpty()) {
            sum += s1.pop();
        }
        if (!s2.isEmpty()) {
            sum += s2.pop();
        }
        carry = sum / 10;
        ListNode node = new ListNode(sum % 10);
        node.next = head;                     // prepend: the second reversal restores the order
        head = node;
    }
    return head;
}

陷阱

  • 兩個堆疊都空了就停:[9, 9] + [1] 會掉最後的進位,回傳 [0, 0] 而不是 [1, 0, 0]。還有進位時也要繼續迴圈。
  • 把每條串列轉成整數再相加,是 add_x_sum § 2-7 的做法;它只在 Python 可行,因為 Python 的整數沒有上限 —— 到 100 位數時 Java 的 long 就溢位了。
  • 先把兩條串列反轉是 O(1) 額外空間,但會修改輸入,而 follow-up(「不要修改輸入串列」)正好排除它。

相似題 — LC 2(最低位在前:不需要堆疊)· LC 234(全部 push,再拿前端和 pop 出來的比)

作用域帳本的題目

LC 題目 完整解在哪裡
388 Longest Absolute File Path stack § Template 6 — 錨點題,外加「覆寫深度槽位」的變形
636 Exclusive Time of Functions stack § Template 6 — 第二個錨點題
71 Simplify Path string § Variation 6.1 — .. 會 pop 掉上一層目錄