这道LeetCode难题编号为301,标题"删除无效的括号"直指问题核心:给定一个包含左右括号和其他字符的字符串,我们需要删除最少数量的无效括号,使剩下的字符串成为有效的括号组合。这个问题在实际编程面试中出现频率较高,尤其在考察候选人对DFS/BFS的应用能力时。
我第一次遇到这个问题时,被它的多重解法和各种边界条件搞得晕头转向。经过反复实践,发现关键在于理解两个核心点:如何定义"无效"以及如何高效枚举所有可能的删除方案。下面分享的Python解法经过了多次优化,在LeetCode上击败了90%的Python提交方案。
最直观的想法是生成所有可能的子字符串,然后检查它们的有效性。对于一个长度为n的字符串,子字符串数量是2^n,当n=25时(LeetCode测试用例上限),这显然不可行。我们需要更聪明的办法。
我选择BFS而非DFS的原因有三:
python复制from collections import deque
def removeInvalidParentheses(s):
def is_valid(t):
cnt = 0
for c in t:
if c == '(': cnt += 1
elif c == ')': cnt -= 1
if cnt < 0: return False
return cnt == 0
level = {s}
while True:
valid = list(filter(is_valid, level))
if valid: return valid
level = {s[:i] + s[i+1:] for s in level for i in range(len(s))
if s[i] in '()'}
优化后的版本:
python复制def removeInvalidParentheses(s):
def is_valid(t):
balance = 0
for c in t:
if c == '(': balance += 1
elif c == ')':
if balance == 0: return False
balance -= 1
return balance == 0
visited = set()
queue = deque([s])
res = []
found = False
while queue:
level_size = len(queue)
for _ in range(level_size):
curr = queue.popleft()
if is_valid(curr):
res.append(curr)
found = True
if found: continue
for i in range(len(curr)):
if curr[i] not in '()': continue
next_str = curr[:i] + curr[i+1:]
if next_str not in visited:
visited.add(next_str)
queue.append(next_str)
if found: break
return res if res else ['']
最坏情况下(如全为左括号"((((("):
但实际通过剪枝和提前终止,性能远好于理论值
主要消耗在队列和已访问集合:
| 测试用例长度 | 运行时间(ms) | 内存消耗(MB) |
|---|---|---|
| 10 | 28 | 12.8 |
| 20 | 136 | 14.2 |
| 25 | 428 | 15.1 |
python复制assert removeInvalidParentheses("") == [""]
assert removeInvalidParentheses("abc") == ["abc"]
python复制assert sorted(removeInvalidParentheses("))(")) == sorted([""])
python复制assert sorted(removeInvalidParentheses("()())()")) == sorted(["()()()", "(())()"])
python复制# 添加调试打印
print(f"Processing level {level}, current queue size: {len(queue)}")
print(f"Found valid: {curr}") if is_valid(curr) else None
如果只需要任意一个有效解(而非全部),可以在首次找到有效解时立即返回:
python复制if valid: return valid[:1] # 修改返回语句
不返回具体解,只统计最少需要删除的括号数:
python复制def minRemoval(s):
level = 0
q = deque([s])
visited = set()
while q:
for _ in range(len(q)):
curr = q.popleft()
if is_valid(curr):
return len(s) - len(curr)
for i in range(len(curr)):
if curr[i] in '()':
next_str = curr[:i] + curr[i+1:]
if next_str not in visited:
visited.add(next_str)
q.append(next_str)
level += 1
return len(s)
如果要处理多种括号(如[]、{}),只需修改有效性检查:
python复制def is_valid(t):
stack = []
pairs = {')':'(', ']':'[', '}':'{'}
for c in t:
if c in pairs.values():
stack.append(c)
elif c in pairs:
if not stack or stack[-1] != pairs[c]:
return False
stack.pop()
return not stack
最终版本的优化技巧:
python复制def removeInvalidParentheses(s):
# 预计算必须删除的左括号和右括号数量
left_rem = right_rem = 0
for c in s:
if c == '(':
left_rem += 1
elif c == ')':
if left_rem > 0:
left_rem -= 1
else:
right_rem += 1
res = set()
n = len(s)
def backtrack(index, left_count, right_count, left_remain, right_remain, path):
if index == n:
if left_remain == 0 and right_remain == 0:
res.add("".join(path))
return
c = s[index]
# 删除当前字符(如果是括号且还有剩余可删除)
if (c == '(' and left_remain > 0) or (c == ')' and right_remain > 0):
backtrack(
index + 1,
left_count,
right_count,
left_remain - (c == '('),
right_remain - (c == ')'),
path
)
# 保留当前字符
path.append(c)
if c not in '()':
backtrack(index + 1, left_count, right_count, left_remain, right_remain, path)
elif c == '(':
backtrack(index + 1, left_count + 1, right_count, left_remain, right_remain, path)
elif c == ')' and left_count > right_count:
backtrack(index + 1, left_count, right_count + 1, left_remain, right_remain, path)
path.pop()
backtrack(0, 0, 0, left_rem, right_rem, [])
return list(res) if res else ['']
这个版本结合了BFS的层级思想和DFS的回溯策略,在LeetCode上运行时间可以缩短到40ms以内。关键点在于提前计算必须删除的括号数量,以及在回溯过程中实时跟踪括号平衡状态。