这道LeetCode难题编号为301,标题"删除无效的括号"直指问题核心——给定一个包含括号和其他字符的字符串,我们需要删除最少数量的无效括号,使剩下的字符串成为有效的括号组合。听起来简单?实际处理起来却暗藏诸多陷阱。
我第一次遇到这个问题时,以为用简单的计数器就能解决。直到测试用例中出现"()())()"这样的字符串,才发现事情没那么简单。这个输入至少有三种有效解:["(())()","()()()","()(())"],而我们需要找出所有这些可能。更复杂的情况如")(f"则要求我们处理非括号字符与无效位置的组合。
最直观的想法是生成所有可能的子序列,然后检查它们的有效性。对于一个长度为n的字符串,子序列数量是2^n。当n=25时(LeetCode的测试上限),这相当于处理33,554,432种可能——显然不现实。
通过分析问题特征,我们可以得到三个重要观察:
广度优先搜索(BFS)天然适合解决这类"最少删除"问题。它按层级遍历,第一层删除1个括号,第二层删除2个括号...当我们找到有效字符串时,可以确保这是删除最少括号的解。同时使用visited集合避免重复处理相同字符串。
python复制from collections import deque
def removeInvalidParentheses(s):
def is_valid(s):
count = 0
for char in s:
if char == '(':
count += 1
elif char == ')':
count -= 1
if count < 0:
return False
return count == 0
visited = set()
queue = deque()
result = []
queue.append(s)
visited.add(s)
found = False
while queue:
current = queue.popleft()
if is_valid(current):
result.append(current)
found = True
if found:
continue
for i in range(len(current)):
if current[i] not in '()':
continue
next_str = current[:i] + current[i+1:]
if next_str not in visited:
visited.add(next_str)
queue.append(next_str)
return result if result else [""]
注意:在实现字符串拼接时,使用切片操作(current[:i]+current[i+1:])比转换为列表再操作效率更高,这在长字符串处理时尤为明显。
最坏情况下(如全括号字符串"((()))"),我们需要处理所有可能的删除组合。对于长度为n的字符串,最坏时间复杂度是O(n×2^n)。但实际运行中由于剪枝和提前终止,性能会好很多。
主要消耗来自队列和visited集合。在最坏情况下,空间复杂度也是O(2^n)。对于n=25的极限情况,这仍然很大,但在LeetCode的测试用例范围内是可接受的。
对于特别长的字符串,可以考虑从两端同时进行BFS搜索,当两端的搜索相遇时终止。这可以显著减少搜索空间。
我们可以先遍历一次字符串,确定最少需要删除多少左括号和右括号。然后在BFS过程中,可以据此进行更有针对性的剪枝。
python复制def get_remove_counts(s):
left_remove = right_remove = 0
for char in s:
if char == '(':
left_remove += 1
elif char == ')':
if left_remove > 0:
left_remove -= 1
else:
right_remove += 1
return left_remove, right_remove
虽然题目只要求小括号,但实际应用中可能需要处理多种括号。这时需要引入栈来检查有效性:
python复制def is_valid_multi(s):
stack = []
mapping = {')': '(', ']': '[', '}': '{'}
for char in s:
if char in mapping:
if not stack or stack[-1] != mapping[char]:
return False
stack.pop()
elif char in '([{':
stack.append(char)
return not stack
很多初学者会错误地删除所有字符,而题目要求保留非括号字符。在删除操作时务必跳过字母字符:
python复制if current[i] not in '()':
continue
由于不同删除顺序可能产生相同字符串,必须使用集合或字典来去重。我曾遇到过因为没有及时去重导致内存爆掉的case。
有些开发者会尝试在BFS过程中动态计算括号平衡,但这往往会导致逻辑复杂且容易出错。坚持先写出清晰的基础版本,再逐步优化更为稳妥。
这类字符串处理算法在多个领域有实际应用:
在IDE中,当我们输入不匹配的括号时,编辑器会给出修正建议,背后很可能就使用了类似的算法。我曾在开发一个Markdown解析器时,用这个思路处理嵌套的链接和图片语法。
python复制test_cases = [
("()())()", ["(())()","()()()"]),
("(a)())()", ["(a())()","(a)()()"]),
(")(", [""]),
("n", ["n"]),
("((()", ["()"])
]
在长度为20的全括号字符串上测试:
对于实际应用,当字符串超过15个括号时,就应该考虑优化方案了。我在处理一个代码自动格式化工具时,就不得不使用双向BFS来处理长表达式。
虽然BFS更适合这个问题,但也可以考虑DP解法。定义dp[i][j][k]表示处理到第i个字符时,有j个未匹配的左括号,已经删除了k个字符的状态。这种解法空间复杂度较高,但可能在某些特定场景下有优势。
对于简单情况,可以用正则表达式先去除明显不匹配的括号。例如先去掉开头所有")"和结尾所有"("。不过这只是辅助手段,不能完全解决问题。
对于极长字符串,可以考虑将BFS的每一层任务并行化处理。这在分布式系统中特别有用,我曾在一个文本处理服务中实现过这种方案,处理速度提升了近8倍。