LeetCode 0301.删除无效的括号:二进制枚举 / 回溯

【LetMeFly】301.删除无效的括号:二进制枚举 / 回溯

力扣题目链接:https://leetcode.cn/problems/remove-invalid-parentheses/

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

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

示例 1:

复制代码
输入:s = "()())()"
输出:["(())()","()()()"]

示例 2:

复制代码
输入:s = "(a)())()"
输出:["(a())()","(a)()()"]

示例 3:

复制代码
输入:s = ")("
输出:[""]

提示:

  • 1 <= s.length <= 25
  • s 由小写英文字母以及括号 '(' 和 ')' 组成
  • s 中至多含 20 个括号

解题方法一:二进制枚举

首先遍历一遍原始字符串得到括号的下标有哪些、得到至少需要移除多少个括号。

关于至少需要移除多少个字符,可以参考昨天的题目《921.使括号有效的最少添加:一次遍历(贪心)》。

简言之就是使用一个变量left记录左括号比右括号多几个,left < 0则说明需要移除当前右括号;以及最终剩下left个未配对的左括号也需要被移除。

假设括号有m个,那么我们可以使用一个m位的二进制数 i ∈ [ 0 , 2 m ) i\in [0, 2^m) i∈[0,2m)来表示保留哪个括号(其中i二进制下第j位为1的话表示整个字符串第j个括号被删除)。

假设要移除k个括号,如果i二进制下恰好有k个1,则构造对应的字符串,并遍历一遍看看该字符串是否合法。

关于一个括号序列字符串是否合法,类似问题"至少需要移除多少个字符使得括号序列合法",如果至少需要移除0个括号则说明原始字符串合法。

时空复杂度

  • 时间复杂度 O ( 2 m + n × C m k ) O(2^m+n\times C_m^k) O(2m+n×Cmk)
  • 空间复杂度 O ( n × C m k ) O(n\times C_m^k) O(n×Cmk)

其中 n = l e n ( s ) n=len(s) n=len(s), m m m是字符串中的括号数量, k k k是至少需要移除括号的数量。

AC代码

C++
cpp 复制代码
/*
 * @LastEditTime: 2026-10-07 11:47:10
 */
class Solution {
private:
    vector<int> idxs;  // 括号位置
    int mini_remove;

    void getInfo(const string& s) {
        int left = 0;
        mini_remove = 0;
        for (int i = 0, n = s.size(); i < n; i++) {
            if (s[i] == '(') {
                idxs.push_back(i);
                left++;
            } else if (s[i] == ')') {
                idxs.push_back(i);
                if (left) {
                    left--;
                } else {
                    mini_remove++;
                }
            }
        }
        mini_remove += left;
    }

    string genS(const string& s, int mask) {
        string this_s;
        this_s.reserve(s.size() - mini_remove);
        vector<int> deleted;
        deleted.reserve(idxs.size());
        for (int i = 0; i < idxs.size(); i++) {
            if (mask >> i & 1) {
                deleted.push_back(idxs[i]);
            }
        }
        for (int is = 0, ic = 0; is < s.size(); is++) {
            if (ic < deleted.size() && is == deleted[ic]) {
                ic++;
            } else {
                this_s.push_back(s[is]);
            }
        }
        return this_s;
    }

    bool ok(const string& s) {
        int left = 0;
        for (int i = 0, n = s.size(); i < n; i++) {
            if (s[i] == '(') {
                left++;
            } else if (s[i] == ')') {
                if (left) {
                    left--;
                } else {
                    return false;
                }
            }
        }
        return !left;
    }
public:
    vector<string> removeInvalidParentheses(const string& s) {
        unordered_set<string> se;
        getInfo(s);
        int parentheses = idxs.size();
        
        for (int i = 0, to = 1 << parentheses; i < to; i++) {
            if (__builtin_popcount(i) != mini_remove) {
                continue;
            }
            string this_s = genS(s, i);
            if (ok(this_s)) {
                se.insert(this_s);
            }
        }
        return vector<string>(se.begin(), se.end());
    }
};

解题方法二:回溯

类似方法一,遍历一次原始字符串得到需要移除多少左括号、需要移除多少右括号。

写一个回溯函数dfs(s, idx, left, left_removed, right_removed),其中:

  • s是原始字符串
  • idx是当前遍历到的下标
  • left是当前已经保留的左括号比右括号多几个
  • left_removed是当前已经移除的左括号数量
  • right_removed是当前已经移除的右括号数量

如果idx == s.size(),则说明已经遍历完了原始字符串,终止递归。递归终止之前看下如果left为零、left_removed和right_removed分别等于需要移除的左右括号数量,则说明找到了一种可行的移除方案,放入答案集合中。

否则开始回溯尝试:

  1. 如果当前字符是左括号(,并且还有左括号可以移除(left_removed < remove_left),则尝试移除当前左括号;
  2. 如果当前字符是右括号),并且还有右括号可以移除(right_removed < remove_right),则尝试移除当前右括号;
  3. 尝试不移除当前字符(只要不是 "left==0并且当前字符是右括号"就可以尝试保留当前字符)。递归结束回来记得弹出当前保留的字符。

时空复杂度

  • 时间复杂度 O ( n × 2 m ) O(n \times 2^m) O(n×2m)
  • 空间复杂度 O ( n × C m k ) O(n\times C_m^k) O(n×Cmk)

其中 n = l e n ( s ) n=len(s) n=len(s), m m m是字符串中的括号数量

AC代码

C++
cpp 复制代码
/*
 * @LastEditTime: 2026-10-07 14:28:52
 */
class Solution {
private:
    int remove_left, remove_right;
    string now;
    unordered_set<string> ans;

    void getInfo(const string& s) {
        remove_left = remove_right = 0;
        for (char c : s) {
            if (c == '(') {
                remove_left++;
            } else if (c == ')') {
                if (remove_left) {
                    remove_left--;
                } else {
                    remove_right++;
                }
            }
        }
    }

    void dfs(const string& s, int idx, int left, int left_removed, int right_removed) {
        if (idx == s.size()) {
            if (left == 0 && left_removed == remove_left && right_removed == remove_right) {
                ans.insert(now);
            }
            return;
        }
        
        if (s[idx] == '(' && left_removed < remove_left) {
            dfs(s, idx + 1, left, left_removed + 1, right_removed);
        }
        if (s[idx] == ')' && right_removed < remove_right) {
            dfs(s, idx + 1, left, left_removed, right_removed + 1);
        }
        // don't remove
        left += s[idx] == '(' ? 1 : s[idx] == ')' ? -1 : 0;
        if (left < 0) {
            return;
        }
        now.push_back(s[idx]);
        dfs(s, idx + 1, left, left_removed, right_removed);
        now.pop_back();
    }
public:
    vector<string> removeInvalidParentheses(const string& s) {
        getInfo(s);
        now.reserve(s.size() - remove_left - remove_right);
        dfs(s, 0, 0, 0, 0);
        return vector<string>(ans.begin(), ans.end());
    }
};

End

今日LeetCode.每日一题连续1906天了。

同步发文于CSDN和我的个人博客,原创不易,转载经作者同意后请附上原文链接哦~

千篇源码题解已开源

相关推荐
Nil2084 小时前
leetcode 322零钱兑换
算法·leetcode·职场和发展
海绵宝宝转agent4 小时前
2026-10-9 leetcode100刷题+面经整理
笔记·算法·面试
yi0114 小时前
DAY24: LeetCode 167:两数之和 II|从暴力枚举到左右双指针
笔记·python·算法·leetcode·二分查找·双指针
木井巳4 小时前
【BFS 解决拓扑排序】课程表
java·算法·leetcode·广度优先·宽度优先·推荐算法·图搜索算法
扶风ff5 小时前
练题簿在线免费刷题:会员线下组卷、Word 试卷与成绩导入,课堂检测更方便
java·开发语言·算法·小程序·word
yi0115 小时前
LeetCode 15:三数之和|排序 + 双指针,如何避免重复答案?
笔记·python·算法·leetcode·双指针
-dzk-5 小时前
【多维动态规划】LC 1143.最长公共子序列
算法·动态规划
C++ 老炮儿的技术栈5 小时前
char (*csConnectName)[256]; 和 char csConnectName [256] 有什么区别
java·c语言·开发语言·c++·人工智能·算法·c
Omics Pro5 小时前
全新可重分析!代谢组质谱专用
数据库·人工智能·算法·机器学习·自然语言处理