LeetCode 解題思路:20. Valid Parentheses(有效的括號)
深入理解堆疊資料結構在括號匹配中的應用
目錄
題目描述
給定一個只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字串 s
,判斷字串是否有效。
有效字串需滿足:
- 左括號必須用相同類型的右括號閉合
- 左括號必須以正確的順序閉合
- 每個右括號都有一個對應的相同類型的左括號
範例
範例 1:
輸入:s = "()"
輸出:true
範例 2:
輸入:s = "()[]{}"
輸出:true
範例 3:
輸入:s = "(]"
輸出:false
範例 4:
輸入:s = "([)]"
輸出:false
解釋:雖然有對應的括號,但順序不正確
範例 5:
輸入:s = "{[]}"
輸出:true
限制條件
1 <= s.length <= 10⁴
s
僅由括號'()[]{}'
組成
核心概念:為什麼要用堆疊?
括號匹配的本質
觀察有效的括號序列,你會發現一個規律:最後出現的左括號,必須最先被匹配。
範例:{[()]}
↓
遇到 { → 需要找 }
遇到 [ → 需要找 ](但要先處理)
遇到 ( → 需要找 )(但要先處理)
遇到 ) → 匹配最近的 (
遇到 ] → 匹配最近的 [
遇到 } → 匹配最近的 {
這正是**後進先出(LIFO)**的特性,而堆疊就是為此而生!
堆疊的視覺化
想像堆疊就像:
- 一疊盤子:最後放上去的必須最先拿走
- 瀏覽器的返回按鈕:最後訪問的頁面最先返回
- 穿衣服和脫衣服:最後穿的最先脫
解法一:使用堆疊(最優解)
思路
- 遇到左括號,壓入堆疊
- 遇到右括號,檢查堆疊頂部是否為對應的左括號
- 最後檢查堆疊是否為空
程式碼實現
class Solution:
def isValid(self, s: str) -> bool:
# 建立括號配對的映射
bracket_map = {
')': '(',
'}': '{',
']': '['
}
# 初始化堆疊
stack = []
# 遍歷每個字符
for char in s:
if char in bracket_map: # 如果是右括號
# 檢查堆疊是否為空,或頂部元素是否匹配
if not stack or stack[-1] != bracket_map[char]:
return False
stack.pop() # 匹配成功,彈出左括號
else: # 如果是左括號
stack.append(char)
# 所有括號都應該被匹配,堆疊應為空
return len(stack) == 0
執行過程視覺化
讓我們詳細追蹤 s = "{[()]}"
:
步驟 1:遇到 '{'
stack: ['{']
動作:壓入堆疊
步驟 2:遇到 '['
stack: ['{', '[']
動作:壓入堆疊
步驟 3:遇到 '('
stack: ['{', '[', '(']
動作:壓入堆疊
步驟 4:遇到 ')'
stack: ['{', '[', '(']
檢查:stack[-1] = '(' = bracket_map[')'] ✓
動作:彈出 '('
stack: ['{', '[']
步驟 5:遇到 ']'
stack: ['{', '[']
檢查:stack[-1] = '[' = bracket_map[']'] ✓
動作:彈出 '['
stack: ['{']
步驟 6:遇到 '}'
stack: ['{']
檢查:stack[-1] = '{' = bracket_map['}'] ✓
動作:彈出 '{'
stack: []
結果:stack 為空,返回 true
無效範例:s = "([)]"
步驟 1-3:壓入 '(', '[', ')'
stack: ['(', '[']
步驟 4:遇到 ']'
stack: ['(', '[']
檢查:stack[-1] = '[' = bracket_map[']'] ✓
動作:彈出 '['
stack: ['(']
步驟 5:遇到 ')'
stack: ['(']
檢查:stack[-1] = '(' = bracket_map[')'] ✓
動作:彈出 '('
stack: []
等等...這樣看起來是有效的?讓我們重新分析!
正確分析 "([)]"
實際上 s = "([)]" 的執行過程:
步驟 1:遇到 '(' → stack: ['(']
步驟 2:遇到 '[' → stack: ['(', '[']
步驟 3:遇到 ')'
檢查:需要 '(',但 stack[-1] = '[' ✗
返回 False
複雜度分析
- 時間複雜度:O(n),其中 n 是字串長度
- 空間複雜度:O(n),最壞情況下(全是左括號)需要存儲 n 個字符
解法二:優化版本(使用字典簡化)
思路
將所有括號對存在字典中,讓程式碼更簡潔。
程式碼實現
class Solution:
def isValid(self, s: str) -> bool:
# 更完整的映射(包含左右括號)
pairs = {
'(': ')',
'[': ']',
'{': '}'
}
stack = []
for char in s:
if char in pairs: # 左括號
stack.append(char)
else: # 右括號
if not stack or pairs[stack.pop()] != char:
return False
return len(stack) == 0
解法三:使用字串替換(不推薦但有趣)
思路
不斷替換掉有效的括號對,直到無法替換。
程式碼實現
class Solution:
def isValid(self, s: str) -> bool:
while '()' in s or '[]' in s or '{}' in s:
s = s.replace('()', '')
s = s.replace('[]', '')
s = s.replace('{}', '')
return s == ''
執行範例
s = "{[]}"
第 1 輪:替換 '[]' → s = "{}"
第 2 輪:替換 '{}' → s = ""
結果:返回 true
s = "([)]"
第 1 輪:沒有相鄰的配對括號
結果:返回 false
為什麼不推薦?
- 時間複雜度:O(n²),因為每次替換都要掃描整個字串
- 空間複雜度:O(n),需要創建新字串
- 效率低下:相比堆疊解法慢很多
常見錯誤與陷阱
錯誤 1:忘記檢查堆疊是否為空
# 錯誤示範
def isValid(self, s: str) -> bool:
stack = []
bracket_map = {')': '(', '}': '{', ']': '['}
for char in s:
if char in bracket_map:
if stack[-1] != bracket_map[char]: # 忘記檢查 stack 是否為空!
return False
stack.pop()
else:
stack.append(char)
return len(stack) == 0
# 測試案例 s = ")" 會造成 IndexError
錯誤 2:忘記最後檢查堆疊
# 錯誤示範
def isValid(self, s: str) -> bool:
# ... 前面的程式碼 ...
for char in s:
# ... 處理邏輯 ...
return True # 錯誤!應該檢查 stack 是否為空
# 測試案例 s = "(" 會錯誤地返回 True
錯誤 3:括號類型混淆
# 容易出錯的寫法
if char == ')' or char == '}' or char == ']':
# 處理右括號
# 更好的寫法
if char in bracket_map: # bracket_map 包含所有右括號
# 處理右括號
進階思考
1. 如果允許其他字符怎麼辦?
def isValid(self, s: str) -> bool:
stack = []
bracket_map = {')': '(', '}': '{', ']': '['}
for char in s:
if char in '({[': # 左括號
stack.append(char)
elif char in ')}]': # 右括號
if not stack or stack[-1] != bracket_map[char]:
return False
stack.pop()
# 其他字符直接忽略
return len(stack) == 0
2. 如果要返回無效的位置?
def findInvalidPosition(self, s: str) -> int:
stack = []
bracket_map = {')': '(', '}': '{', ']': '['}
for i, char in enumerate(s):
if char in bracket_map:
if not stack or stack[-1][0] != bracket_map[char]:
return i # 返回無效右括號的位置
stack.pop()
else:
stack.append((char, i))
# 如果還有未匹配的左括號
if stack:
return stack[0][1] # 返回第一個未匹配左括號的位置
return -1 # 全部有效
3. 計算需要添加多少括號使其有效?
這是 LeetCode 921 題的變形,可以分別計算未匹配的左右括號數量。
效能優化技巧
1. 提前終止
def isValid(self, s: str) -> bool:
# 奇數長度不可能有效
if len(s) % 2 == 1:
return False
# ... 其餘邏輯
2. 使用 collections.deque
對於大量操作,deque
的效能可能更好:
from collections import deque
def isValid(self, s: str) -> bool:
stack = deque()
# ... 使用 stack.append() 和 stack.pop()
相關題目
- LeetCode 22: Generate Parentheses - 生成有效括號組合
- LeetCode 32: Longest Valid Parentheses - 最長有效括號子串
- LeetCode 921: Minimum Add to Make Parentheses Valid - 使括號有效的最少添加
- LeetCode 1249: Minimum Remove to Make Valid Parentheses - 移除無效的括號
總結
這道題是堆疊應用的經典範例,掌握它你將學會:
- 堆疊的基本操作:push、pop、peek、isEmpty
- 括號匹配的思維:後進先出的應用
- 邊界條件處理:空堆疊、剩餘元素的檢查
- 程式碼優化技巧:使用映射表簡化邏輯
記住核心概念:遇到左括號就壓入,遇到右括號就匹配,最後檢查是否清空。
練習建議
- 先手動模擬幾個範例,理解堆疊的運作
- 嘗試不看答案寫出基本版本
- 思考各種邊界情況(空字串、全左括號、全右括號)
- 挑戰進階變形題目,加深理解