题目描述

✅ 301. 删除无效的括号

image-20260928220513290

题意分析

只能删除括号,字母及剩余字符的相对顺序必须保留。要求先让删除数量最少,再返回这个最少删除数量下所有不同的合法字符串。

合法括号串要满足两点:从左到右的任意前缀中,右括号都不能多于左括号;扫描结束后,左右括号数量相等。字母不参与配对,空字符串也合法。

解法:BFS 按删除次数分层

核心思路

[!blue]

把每个字符串看成一个状态,删除其中一个括号就到达下一个状态。每次操作的代价都是 1,因此可以按删除次数做 BFS:第 0 层是原串,第 d 层保存恰好删除 d 个括号后得到的不同字符串。

先检查当前层的全部状态。若这一层出现合法字符串,那么更浅的层已经检查过且都无解,当前删除次数就是最少的;同层的所有合法串都要收集,随后立即返回,不再向下一层删除。若当前层无解,才生成下一层。

合法性检查用 balance 记录已经读到但尚未匹配的左括号数。遇到 ( 加一,遇到 ) 减一。若途中变负,说明这个右括号前面没有左括号可配对,后面的字符也无法补救;扫描结束时还必须为 0,避免留下未匹配的左括号。

删除过程有两种重复:同一段连续相同括号中删任意一个,得到的字符串相同,所以只枚举第一个;不同删除顺序也可能到达同一个字符串,用 visited 只保留首次生成的状态。状态本身已经完整决定后续能删出什么,舍弃重复路径不会丢失答案。

解题步骤

  1. 将原串放入 level,同时加入 visited,表示删除 0 次。
  2. 遍历 level,用前缀余额检查合法性,把所有合法串加入 answer。
  3. 若 answer 非空,返回整个列表;即使唯一答案是空字符串,列表仍然非空。
  4. 否则枚举每个状态中的括号位置,跳过字母,以及与前一个字符相同的括号。
  5. 删除当前括号,拼接它前后的两段;只有 visited 中尚不存在的新字符串才能加入 nextLevel。
  6. 用 nextLevel 替换 level,继续检查下一层。最迟删除全部括号后,剩下的字母串一定合法。

代码实现

class Solution {
    public List<String> removeInvalidParentheses(String s) {
        List<String> level = new ArrayList<>();

        level.add(s);
        Set<String> visited = new HashSet<>();

        visited.add(s);

        while (!level.isEmpty()) {
            List<String> answer = new ArrayList<>();

            for (String cur : level) {
                if (isValid(cur)) {
                    answer.add(cur);
                }
            }

            // 检查完整层再返回,才能收集所有最少删除答案。
            if (!answer.isEmpty()) {
                return answer;
            }

            List<String> nextLevel = new ArrayList<>();

            for (String cur : level) {
                for (int i = 0; i < cur.length(); i++) {
                    char ch = cur.charAt(i);

                    if (ch != '(' && ch != ')') {
                        continue;
                    }

                    // 同一段相同括号删任意一个结果相同,只保留首个选择。
                    if (i > 0 && ch == cur.charAt(i - 1)) {
                        continue;
                    }

                    String next = cur.substring(0, i) + cur.substring(i + 1);

                    if (visited.add(next)) {
                        nextLevel.add(next);
                    }
                }
            }

            level = nextLevel;
        }

        return new ArrayList<>();
    }

    private boolean isValid(String s) {
        int balance = 0;

        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);

            if (ch == '(') {
                balance++;
            } else if (ch == ')') {
                balance--;

                if (balance < 0) {
                    return false;
                }
            }
        }

        return balance == 0;
    }
}
func removeInvalidParentheses(s string) []string {
    visited := map[string]bool{s: true}
    level := []string{
        s,
    }

    for len(level) > 0 {
        answer := make([]string, 0)
        for _, cur := range level {
            if isValidParentheses(cur) {
                answer = append(answer, cur)
            }
        }
        // 检查完整层再返回,才能收集所有最少删除答案。
        if len(answer) > 0 {
            return answer
        }

        nextLevel := make([]string, 0)
        for _, cur := range level {
            for i := 0; i < len(cur); i++ {
                if cur[i] != '(' && cur[i] != ')' {
                    continue
                }
                // 同一段相同括号删任意一个结果相同,只保留首个选择。
                if i > 0 && cur[i] == cur[i-1] {
                    continue
                }
                next := cur[:i] + cur[i+1:]
                if !visited[next] {
                    visited[next] = true
                    nextLevel = append(nextLevel, next)
                }
            }
        }
        level = nextLevel
    }
    return nil
}

func isValidParentheses(s string) bool {
    balance := 0
    for i := 0; i < len(s); i++ {
        if s[i] == '(' {
            balance++
        } else if s[i] == ')' {
            balance--
            if balance < 0 {
                return false
            }
        }
    }
    return balance == 0
}

复杂度分析

设字符串长为 n,括号数量为 p。

  • 时间复杂度:最坏 $O(n(p+1)2^p)$,状态至多 $2^p$ 个,每个状态做合法性扫描,并尝试至多 p 次删除与字符串构造;即使没有括号也要检查原串。
  • 空间复杂度:$O(n2^p)$,保存访问集合及当前、下一层的字符串。

关键点总结

[!green]

  • BFS 层数等于删除次数,首个存在合法状态的层给出最少删除次数。
  • 在这一层收集全部合法状态,才能得到所有最优结果。
  • 合法性由“任意前缀余额非负,最终余额为零”共同保证。
  • 去重针对结果字符串,不同删除位置或顺序无需分别保留。

易错点总结

[!yellow]

  • 不能找到第一个合法串就返回,否则会漏掉同层其他答案;也不能继续收集更深层的合法串,否则会混入非最少删除的结果。
  • 只检查最终左右括号数量相等不够,右括号先出现也会造成非法前缀。
  • 只能跳过连续相同括号的等价删除,不能把相同类型括号的所有删除位置都视为等价。
  • 原串本就合法时应直接返回它,不能跳过第 0 层。
  • 空字符串是一个合法结果,不能把包含空字符串的答案列表误判为无解。

相似题目

题目 难度 关联与区别
1249. 移除无效的括号 中等 两题都最少删除括号,原题只需任意一个合法结果,本题要输出全部最优结果并去重。
22. 括号生成 中等 合法前缀的左右括号约束相同,原题从空串生成,本题只能从给定串中删除。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2025/85766111
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!