↓快轉到主要內容
  1. LeetCode/

LeetCode 301: Remove Invalid Parentheses

·2 分鐘· ·
LeetCode Hard Backtracking Bfs String
Wei Yi Chung
作者
Wei Yi Chung
Working at the contributing of open source, distributed systems, and data engineering.
目錄

基本資料
#

難易度: hard 第一次嘗試:2026-09-10

  • 提交紀錄:Time Limit Exceeded → Accepted ×2(回溯)→ Accepted ×2(BFS)

解題思路
#

要刪掉最少的括號讓字串合法,並回傳所有可能的結果。

第一步:算出最少要刪幾個
#

從左到右掃,用一個計數器記錄「還沒配對的 (」:

  • 遇到 (:計數加 1。
  • 遇到 ):有未配對的 ( 就配對,計數減 1;沒有的話,這個 ) 一定要刪,r_rem += 1。
  • 掃完之後,剩下的未配對 ( 數量就是 l_rem。

第二步:回溯,只允許刪 l_rem 個 ( 和 r_rem 個 )
#

對每個括號有「刪」和「留」兩種選擇:

  • 刪:只有對應的額度還有剩才能刪。
  • 留 ):只有目前左括號比右括號多才能留,不然字串前綴就已經不合法。

走到結尾時,額度剛好用完就是一個答案。用 set 去重。

我的嘗試:沒有限制刪除數量,TLE
#

第一版對每個字元都試「刪」和「留」,走到結尾才比較誰刪得最少。這等於枚舉所有 2^n 個子序列,n = 25 時太慢。

先算出 l_rem、r_rem,就能把不可能是最少刪除數的分支提早剪掉。

解法一:回溯 + 刪除額度
#

class Solution:
    def removeInvalidParentheses(self, s: str) -> List[str]:
        l_rem = r_rem = 0
        for ch in s:
            if ch == "(":
                l_rem += 1
            elif ch == ")":
                if l_rem > 0:
                    l_rem -= 1
                else:
                    r_rem += 1

        n = len(s)
        res = set()

        def backtrack(i: int, cur: str, open_count: int, l_rem: int, r_rem: int) -> None:
            if i == n:
                if l_rem == 0 and r_rem == 0:
                    res.add(cur)
                return
            ch = s[i]
            if ch == "(" and l_rem > 0:
                backtrack(i + 1, cur, open_count, l_rem - 1, r_rem)        # 刪
            if ch == ")" and r_rem > 0:
                backtrack(i + 1, cur, open_count, l_rem, r_rem - 1)        # 刪
            if ch == "(":
                backtrack(i + 1, cur + ch, open_count + 1, l_rem, r_rem)   # 留
            elif ch == ")":
                if open_count > 0:
                    backtrack(i + 1, cur + ch, open_count - 1, l_rem, r_rem)
            else:
                backtrack(i + 1, cur + ch, open_count, l_rem, r_rem)

        backtrack(0, "", 0, l_rem, r_rem)
        return list(res)

open_count 是目前「留下的 ( 減掉留下的 )」,原本的版本用兩個計數器 l_count、r_count,意思一樣。

解法二:BFS,一層刪一個括號
#

class Solution:
    def removeInvalidParentheses(self, s: str) -> List[str]:
        def is_valid(t: str) -> bool:
            count = 0
            for ch in t:
                if ch == "(":
                    count += 1
                elif ch == ")":
                    count -= 1
                    if count < 0:
                        return False
            return count == 0

        level = {s}
        while level:
            valid = [t for t in level if is_valid(t)]
            if valid:
                return valid
            level = {t[:i] + t[i + 1:] for t in level for i in range(len(t)) if t[i] in "()"}
        return [""]

第 k 層是「刪掉 k 個括號的所有字串」,第一個出現合法字串的層就是最少刪除數。這版跑得比回溯快很多(61ms 對 1356ms),因為最少刪除數通常很小,BFS 很早就停了。

複雜度
#

兩種做法最壞都是指數級,O(2^n · n)。實際上回溯靠額度剪枝,BFS 靠提早停止。

要特別避免的錯誤
#

  • 沒有先算出最少刪除數,就枚舉所有子序列。
  • 留下 ) 時沒有檢查前綴是否還合法。
  • 沒有去重:刪掉相鄰的同一種括號會得到同樣的字串。

面試口說整理
#

我先掃一次算出最少要刪幾個 ( 和幾個 ):多出來的 ) 一定要刪,最後沒配對的 ( 也要刪。接著回溯,每個括號可以刪(額度還有剩時)或留(留 ) 時前面要有未配對的 (),走到結尾額度剛好用完就加入答案,用 set 去重。另一種做法是 BFS 每層刪一個括號,第一個出現合法字串的層就是答案。

相關文章

LeetCode 996: Number of Squareful Arrays
·2 分鐘
LeetCode Hard Dynamic-Programming Bitmask Backtracking Math Hash-Table
LeetCode 996 解題筆記:n 最多 12,用 bitmask DP:dp[mask][last] = 用了 mask 這些數、最後一個是 last 的排列數;相同的數規定依 index 順序使用來去重
LeetCode 864: Shortest Path to Get All Keys
·2 分鐘
LeetCode Hard Bfs Bit-Manipulation Matrix
LeetCode 864 解題筆記:BFS 的狀態是 (位置, 拿到的鑰匙遮罩),同一格拿著不同鑰匙是不同的節點
LeetCode 2193: Minimum Number of Moves to Make Palindrome
·1 分鐘
LeetCode Hard Two-Pointers Greedy String Binary-Indexed-Tree
LeetCode 2193 解題筆記:貪心處理最外層:拿最後一個字元,把它最左邊的同伴換到最前面,花費是那個位置;落單的字元最後在正中間,花費是它到中間的距離
LeetCode 420: Strong Password Checker
·2 分鐘
LeetCode Hard Greedy String
LeetCode 420 解題筆記:分長度三種情況,太長時優先刪除 len % 3 == 0 的重複段來抵銷取代次數
LeetCode 1377: Frog Position After T Seconds
·2 分鐘
LeetCode Hard Tree Dfs Bfs Graph Probability
LeetCode 1377 解題筆記:無向邊、seen 要記節點、提早到達目標時要看還能不能走
LeetCode 1320: Minimum Distance to Type a Word Using Two Fingers
·1 分鐘
LeetCode Hard Dynamic-Programming State-Machine String
LeetCode 1320 解題筆記:打完 word[i] 時,一定有一根手指在 word[i] 上,狀態只要記另一根手指在哪;dp[other] 是另一根手指在 other 的最小距離