删除无效的括号
问题简介
[题解github地址](https://github.com/swf2020/LeetCode-Hot100-Solutions)
题目描述
给你一个由若干括号和字母组成的字符串 s,删除最小数量的无效括号,使得输入的字符串有效。
返回所有可能的结果。答案可以按任意顺序返回。
示例说明
✅ 示例 1:
- 输入:
s = "()())()" - 输出:
["(())()","()()()"]
✅ 示例 2:
- 输入:
s = "(a)())()" - 输出:
["(a())()","(a)()()"]
✅ 示例 3:
- 输入:
s = ")(" - 输出:
[""]
解题思路
方法一:BFS(广度优先搜索)✅
💡 核心思想:由于要求删除最少数量的括号,BFS 天然适合解决最短路径/最少操作类问题。
步骤分解:
- 初始化:将原始字符串加入队列
- 层级遍历 :
- 对于当前层级的所有字符串,检查是否有效
- 如果找到有效字符串,直接返回该层级所有有效结果(因为 BFS 保证了最少删除)
- 如果没有有效字符串,生成下一层级的所有可能(删除一个括号)
- 去重:使用 Set 避免重复处理相同字符串
- 有效性检查:通过计数器验证括号匹配
方法二:DFS + 剪枝 ✅
💡 核心思想:先计算需要删除的左右括号数量,然后通过 DFS 枚举所有可能的删除方案。
步骤分解:
- 预处理 :计算需要删除的左括号
leftRemove和右括号rightRemove数量 - DFS 回溯 :
- 遍历字符串每个字符
- 对于左括号:可以选择保留或删除(如果还有删除配额)
- 对于右括号:可以选择保留或删除(如果还有删除配额),但保留时需要确保不会造成右括号过多
- 对于其他字符:直接保留
- 剪枝优化 :
- 提前计算删除数量,避免无效搜索
- 在构建过程中实时检查有效性
方法三:递归枚举(暴力法)❌
虽然可行,但时间复杂度过高,不推荐在实际面试中使用。
代码实现
java
// Java 实现 - BFS 方法
import java.util.*;
public class Solution {
public List<String> removeInvalidParentheses(String s) {
List<String> result = new ArrayList<>();
Queue<String> queue = new LinkedList<>();
Set<String> visited = new HashSet<>();
queue.offer(s);
visited.add(s);
boolean found = false;
while (!queue.isEmpty() && !found) {
int size = queue.size();
for (int i = 0; i < size; i++) {
String current = queue.poll();
if (isValid(current)) {
result.add(current);
found = true;
}
// 如果已经找到有效字符串,不需要继续生成下一层
if (found) continue;
// 生成所有删除一个字符的可能
for (int j = 0; j < current.length(); j++) {
char c = current.charAt(j);
// 只考虑删除括号
if (c != '(' && c != ')') continue;
String next = current.substring(0, j) + current.substring(j + 1);
if (!visited.contains(next)) {
queue.offer(next);
visited.add(next);
}
}
}
}
return result;
}
private boolean isValid(String s) {
int count = 0;
for (char c : s.toCharArray()) {
if (c == '(') {
count++;
} else if (c == ')') {
count--;
if (count < 0) return false;
}
}
return count == 0;
}
}
// Java 实现 - DFS 方法
class Solution {
public List<String> removeInvalidParentheses(String s) {
Set<String> result = new HashSet<>();
int[] removes = getMinRemoves(s);
dfs(s, 0, 0, removes[0], removes[1], new StringBuilder(), result);
return new ArrayList<>(result);
}
private int[] getMinRemoves(String s) {
int leftRemove = 0, rightRemove = 0;
for (char c : s.toCharArray()) {
if (c == '(') {
leftRemove++;
} else if (c == ')') {
if (leftRemove > 0) {
leftRemove--;
} else {
rightRemove++;
}
}
}
return new int[]{leftRemove, rightRemove};
}
private void dfs(String s, int index, int leftCount, int leftRemove, int rightRemove,
StringBuilder path, Set<String> result) {
if (index == s.length()) {
if (leftRemove == 0 && rightRemove == 0) {
result.add(path.toString());
}
return;
}
char c = s.charAt(index);
if (c == '(') {
// 选择1: 删除当前左括号
if (leftRemove > 0) {
dfs(s, index + 1, leftCount, leftRemove - 1, rightRemove, path, result);
}
// 选择2: 保留当前左括号
path.append(c);
dfs(s, index + 1, leftCount + 1, leftRemove, rightRemove, path, result);
path.deleteCharAt(path.length() - 1);
} else if (c == ')') {
// 选择1: 删除当前右括号
if (rightRemove > 0) {
dfs(s, index + 1, leftCount, leftRemove, rightRemove - 1, path, result);
}
// 选择2: 保留当前右括号(只有当左括号数量足够时)
if (leftCount > 0) {
path.append(c);
dfs(s, index + 1, leftCount - 1, leftRemove, rightRemove, path, result);
path.deleteCharAt(path.length() - 1);
}
} else {
// 非括号字符,直接保留
path.append(c);
dfs(s, index + 1, leftCount, leftRemove, rightRemove, path, result);
path.deleteCharAt(path.length() - 1);
}
}
}
go
// Go 实现 - BFS 方法
func removeInvalidParentheses(s string) []string {
result := []string{}
queue := []string{s}
visited := make(map[string]bool)
visited[s] = true
found := false
for len(queue) > 0 && !found {
size := len(queue)
for i := 0; i < size; i++ {
current := queue[0]
queue = queue[1:]
if isValid(current) {
result = append(result, current)
found = true
}
if found {
continue
}
for j := 0; j < len(current); j++ {
c := current[j]
if c != '(' && c != ')' {
continue
}
next := current[:j] + current[j+1:]
if !visited[next] {
queue = append(queue, next)
visited[next] = true
}
}
}
}
return result
}
func isValid(s string) bool {
count := 0
for i := 0; i < len(s); i++ {
if s[i] == '(' {
count++
} else if s[i] == ')' {
count--
if count < 0 {
return false
}
}
}
return count == 0
}
// Go 实现 - DFS 方法
func removeInvalidParentheses(s string) []string {
removes := getMinRemoves(s)
result := make(map[string]bool)
var path strings.Builder
dfs(s, 0, 0, removes[0], removes[1], &path, result)
res := make([]string, 0, len(result))
for str := range result {
res = append(res, str)
}
return res
}
func getMinRemoves(s string) [2]int {
leftRemove, rightRemove := 0, 0
for i := 0; i < len(s); i++ {
if s[i] == '(' {
leftRemove++
} else if s[i] == ')' {
if leftRemove > 0 {
leftRemove--
} else {
rightRemove++
}
}
}
return [2]int{leftRemove, rightRemove}
}
func dfs(s string, index, leftCount, leftRemove, rightRemove int, path *strings.Builder, result map[string]bool) {
if index == len(s) {
if leftRemove == 0 && rightRemove == 0 {
result[path.String()] = true
}
return
}
c := s[index]
if c == '(' {
// 删除当前左括号
if leftRemove > 0 {
dfs(s, index+1, leftCount, leftRemove-1, rightRemove, path, result)
}
// 保留当前左括号
path.WriteByte(c)
dfs(s, index+1, leftCount+1, leftRemove, rightRemove, path, result)
path.Truncate(path.Len() - 1)
} else if c == ')' {
// 删除当前右括号
if rightRemove > 0 {
dfs(s, index+1, leftCount, leftRemove, rightRemove-1, path, result)
}
// 保留当前右括号
if leftCount > 0 {
path.WriteByte(c)
dfs(s, index+1, leftCount-1, leftRemove, rightRemove, path, result)
path.Truncate(path.Len() - 1)
}
} else {
// 非括号字符
path.WriteByte(c)
dfs(s, index+1, leftCount, leftRemove, rightRemove, path, result)
path.Truncate(path.Len() - 1)
}
}
示例演示
📌 以 s = "()())()" 为例:
BFS 执行过程:
- Level 0:
"()())()" - Level 1: 检查
"()())()"→ 无效- 生成:
")())()","(()))()","()))()","()())","()())("
- 生成:
- Level 2: 检查 Level 1 的所有字符串
- 发现
"(())()"和"()()()"有效 - 返回结果
- 发现
DFS 执行过程:
- 预处理:需要删除 1 个右括号
- 递归遍历,在第 4 个位置(0-indexed)的右括号处进行删除决策
- 生成两种有效结果
答案有效性证明
✅ 正确性保证:
-
BFS 方法:
- 由于 BFS 按层级遍历,第一次找到的有效字符串必然对应最少删除次数
- 收集同一层级所有有效结果,保证答案完整性
-
DFS 方法:
- 通过预处理准确计算需要删除的括号数量
- 在递归过程中维护左括号计数,确保不会产生无效的中间状态
- 使用 Set 去重,避免重复答案
✅ 边界情况处理:
- 空字符串:正确返回
[""] - 全为无效括号:如
")("返回[""] - 包含字母:正确保留非括号字符
复杂度分析
| 方法 | 时间复杂度 | 空间复杂度 | 适用场景 |
|---|---|---|---|
| BFS | O(2^N) | O(2^N) | 字符串较短,需要保证最少删除 |
| DFS | O(2^N) | O(N) | 字符串较长,内存敏感 |
💡 详细分析:
- 时间复杂度:最坏情况下需要考虑所有可能的子序列,即 O(2^N)
- 空间复杂度 :
- BFS 需要存储所有层级的字符串,最坏 O(2^N)
- DFS 只需要递归栈空间 O(N),加上结果存储空间
问题总结
📌 关键要点:
- 最少删除约束:BFS 天然适合解决最少操作问题
- 有效性验证:简单的计数器即可验证括号匹配
- 去重必要性:避免重复处理相同字符串,提高效率
- 剪枝优化:DFS 中通过预计算删除数量大幅减少搜索空间
💡 面试建议:
- 优先考虑 BFS 方法,思路清晰且易于理解
- 如果面试官要求优化空间复杂度,再介绍 DFS 方法
- 注意处理边界情况和去重逻辑
✅ 扩展思考:
- 如果要求按字典序返回结果,如何修改?
- 如果字符串很长但无效括号很少,哪种方法更优?