(LeetCode-Hot100)301. 删除无效的括号

删除无效的括号

问题简介

LeetCode 301. 删除无效的括号

复制代码
[题解github地址](https://github.com/swf2020/LeetCode-Hot100-Solutions)

题目描述

给你一个由若干括号和字母组成的字符串 s,删除最小数量的无效括号,使得输入的字符串有效。

返回所有可能的结果。答案可以按任意顺序返回。

示例说明

示例 1:

  • 输入:s = "()())()"
  • 输出:["(())()","()()()"]

示例 2:

  • 输入:s = "(a)())()"
  • 输出:["(a())()","(a)()()"]

示例 3:

  • 输入:s = ")("
  • 输出:[""]

解题思路

方法一:BFS(广度优先搜索)✅

💡 核心思想:由于要求删除最少数量的括号,BFS 天然适合解决最短路径/最少操作类问题。

步骤分解:

  1. 初始化:将原始字符串加入队列
  2. 层级遍历
    • 对于当前层级的所有字符串,检查是否有效
    • 如果找到有效字符串,直接返回该层级所有有效结果(因为 BFS 保证了最少删除)
    • 如果没有有效字符串,生成下一层级的所有可能(删除一个括号)
  3. 去重:使用 Set 避免重复处理相同字符串
  4. 有效性检查:通过计数器验证括号匹配

方法二:DFS + 剪枝 ✅

💡 核心思想:先计算需要删除的左右括号数量,然后通过 DFS 枚举所有可能的删除方案。

步骤分解:

  1. 预处理 :计算需要删除的左括号 leftRemove 和右括号 rightRemove 数量
  2. DFS 回溯
    • 遍历字符串每个字符
    • 对于左括号:可以选择保留或删除(如果还有删除配额)
    • 对于右括号:可以选择保留或删除(如果还有删除配额),但保留时需要确保不会造成右括号过多
    • 对于其他字符:直接保留
  3. 剪枝优化
    • 提前计算删除数量,避免无效搜索
    • 在构建过程中实时检查有效性

方法三:递归枚举(暴力法)❌

虽然可行,但时间复杂度过高,不推荐在实际面试中使用。

代码实现

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)的右括号处进行删除决策
  • 生成两种有效结果

答案有效性证明

正确性保证:

  1. BFS 方法

    • 由于 BFS 按层级遍历,第一次找到的有效字符串必然对应最少删除次数
    • 收集同一层级所有有效结果,保证答案完整性
  2. 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),加上结果存储空间

问题总结

📌 关键要点:

  1. 最少删除约束:BFS 天然适合解决最少操作问题
  2. 有效性验证:简单的计数器即可验证括号匹配
  3. 去重必要性:避免重复处理相同字符串,提高效率
  4. 剪枝优化:DFS 中通过预计算删除数量大幅减少搜索空间

💡 面试建议:

  • 优先考虑 BFS 方法,思路清晰且易于理解
  • 如果面试官要求优化空间复杂度,再介绍 DFS 方法
  • 注意处理边界情况和去重逻辑

扩展思考:

  • 如果要求按字典序返回结果,如何修改?
  • 如果字符串很长但无效括号很少,哪种方法更优?
相关推荐
追随者永远是胜利者2 小时前
(LeetCode-Hot100)239. 滑动窗口最大值
java·算法·leetcode·职场和发展·go
今心上2 小时前
spring中的@Autowired到底是什么
java·后端·spring
im_AMBER2 小时前
Leetcode 126 两数之和 II - 输入有序数组 | 盛最多水的容器
数据结构·学习·算法·leetcode
ShiJiuD6668889992 小时前
Java 异常 File
java·开发语言
lxl13072 小时前
C++算法(5)位运算
java·c++·算法
tankeven2 小时前
HJ96 表示数字
c++·算法
嵌入式×边缘AI:打怪升级日志2 小时前
C语言算术赋值运算复习笔记
c语言·stm32·单片机·算法·51单片机·proteus·代码
lxl13072 小时前
C++算法(4)前缀和
开发语言·c++·算法
不想看见4042 小时前
Minimum Path Sum 基本动态规划:二维--力扣101算法题解笔记
算法·leetcode·动态规划