Remove the minimum number of invalid parentheses in order to make the input string valid. Return all possible results.

Note: The input string may contain letters other than the parentheses ( and ).

Example 1:

  1. Input: "()())()"
  2. Output: ["()()()", "(())()"]

Example 2:

  1. Input: "(a)())()"
  2. Output: ["(a)()()", "(a())()"]

Example 3:

  1. Input: ")("
  2. Output: [""]

Credits:
Special thanks to @hpplayer for adding this problem and creating all test cases.

Subscribe to see which companies asked this question

 
这道题让移除最少的括号使得给定字符串为一个合法的含有括号的字符串,我们从小数学里就有括号,所以应该对合法的含有括号的字符串并不陌生,字符串中的左右括号数应该相同,而且每个右括号左边一定有其对应的左括号,而且题目中给的例子也说明了去除方法不唯一,需要找出所有合法的取法。参考了网上大神的解法,这道题首先可以用 BFS 来解,我把给定字符串排入队中,然后取出检测其是否合法,若合法直接返回,不合法的话,对其进行遍历,对于遇到的左右括号的字符,去掉括号字符生成一个新的字符串,如果这个字符串之前没有遇到过,将其排入队中,用 HashSet 记录一个字符串是否出现过。对队列中的每个元素都进行相同的操作,直到队列为空还没找到合法的字符串的话,那就返回空集,参见代码如下:

解法一:

  1. class Solution {
  2. public:
  3. vector<string> removeInvalidParentheses(string s) {
  4. vector<string> res;
  5. unordered_set<string> visited{{s}};
  6. queue<string> q{{s}};
  7. bool found = false;
  8. while (!q.empty()) {
  9. string t = q.front(); q.pop();
  10. if (isValid(t)) {
  11. res.push_back(t);
  12. found = true;
  13. }
  14. if (found) continue;
  15. for (int i = ; i < t.size(); ++i) {
  16. if (t[i] != '(' && t[i] != ')') continue;
  17. string str = t.substr(, i) + t.substr(i + );
  18. if (!visited.count(str)) {
  19. q.push(str);
  20. visited.insert(str);
  21. }
  22. }
  23. }
  24. return res;
  25. }
  26. bool isValid(string t) {
  27. int cnt = ;
  28. for (int i = ; i < t.size(); ++i) {
  29. if (t[i] == '(') ++cnt;
  30. else if (t[i] == ')' && --cnt < ) return false;
  31. }
  32. return cnt == ;
  33. }
  34. };

下面来看一种递归解法,这种解法首先统计了多余的半括号的数量,用 cnt1 表示多余的左括号,cnt2 表示多余的右括号,因为给定字符串左右括号要么一样多,要么左括号多,要么右括号多,也可能左右括号都多,比如 ")("。所以 cnt1 和 cnt2 要么都为0,要么都大于0,要么一个为0,另一个大于0。好,下面进入递归函数,首先判断,如果当 cnt1 和 cnt2 都为0时,说明此时左右括号个数相等了,调用 isValid 子函数来判断是否正确,正确的话加入结果 res 中并返回即可。否则从 start 开始遍历,这里的变量 start 表示当前递归开始的位置,不需要每次都从头开始,会有大量重复计算。而且对于多个相同的半括号在一起,只删除第一个,比如 "())",这里有两个右括号,不管删第一个还是删第二个右括号都会得到 "()",没有区别,所以只用算一次就行了,通过和上一个字符比较,如果不相同,说明是第一个右括号,如果相同则直接跳过。此时来看如果 cnt1 大于0,说明此时左括号多,而如果当前字符正好是左括号的时候,可以删掉当前左括号,继续调用递归,此时 cnt1 的值就应该减1,因为已经删掉了一个左括号。同理,如果 cnt2 大于0,说明此时右括号多,而如果当前字符正好是右括号的时候,可以删掉当前右括号,继续调用递归,此时 cnt2 的值就应该减1,因为已经删掉了一个右括号,参见代码如下:

解法二:

  1. class Solution {
  2. public:
  3. vector<string> removeInvalidParentheses(string s) {
  4. vector<string> res;
  5. int cnt1 = , cnt2 = ;
  6. for (char c : s) {
  7. cnt1 += (c == '(');
  8. if (cnt1 == ) cnt2 += (c == ')');
  9. else cnt1 -= (c == ')');
  10. }
  11. helper(s, , cnt1, cnt2, res);
  12. return res;
  13. }
  14. void helper(string s, int start, int cnt1, int cnt2, vector<string>& res) {
  15. if (cnt1 == && cnt2 == ) {
  16. if (isValid(s)) res.push_back(s);
  17. return;
  18. }
  19. for (int i = start; i < s.size(); ++i) {
  20. if (i != start && s[i] == s[i - ]) continue;
  21. if (cnt1 > && s[i] == '(') {
  22. helper(s.substr(, i) + s.substr(i + ), i, cnt1 - , cnt2, res);
  23. }
  24. if (cnt2 > && s[i] == ')') {
  25. helper(s.substr(, i) + s.substr(i + ), i, cnt1, cnt2 - , res);
  26. }
  27. }
  28. }
  29. bool isValid(string t) {
  30. int cnt = ;
  31. for (int i = ; i < t.size(); ++i) {
  32. if (t[i] == '(') ++cnt;
  33. else if (t[i] == ')' && --cnt < ) return false;
  34. }
  35. return cnt == ;
  36. }
  37. };

下面这种解法是论坛上的高票解法,思路确实很巧妙。递归函数的参数中,last_i 表示当前遍历到的位置,相当上面解法中的 start,last_j 表示上一个删除的位置,这样可以避免重复计算。然后有个括号字符数组,初始化时放入左括号和右括号,博主认为这个字符数组是此解法最精髓的地方,因为其顺序可以改变,可以变成反向括号,这个就比较叼了,后面再讲它到底有多叼吧。在递归函数中,从 last_i 开始遍历,在找正向括号的时候,用变量 cnt 表示括号数组中的左括号出现的次数,遇到左括号自增1,遇到右括号自减1。当左括号大于等于右括号的时候,直接跳过。这个循环的目的是要删除多余的右括号,所以当 cnt 小于0的时候,从上一个删除位置 last_j 开始遍历,如果当前是右括号,且是第一个右括号(关于这块可以参见上面解法中的分析),删除当前右括号,并调用递归函数。注意这个 for 循环结束后要直接返回,因为进这个 for 循环的都是右括号多的,删到最后最多是删成和左括号一样多,不需要再去翻转删左括号。好,最后来说这个最叼的翻转,当字符串的左括号个数大于等于右括号的时候,不会进入第二个 for 循环,自然也不会 return。那么由于左括号的个数可能会要大于右括号,所以还要删除多余的左括号,将字符串反转一下,比如 "(()",反转变成 ")((",此时虽然还是要删除多余的左括号,但是反转后就没有合法的括号了,所以变成了找反向括号 ")(",还是可以删除多余的左括号,然后判断此时括号数组的状态,如果是正向括号,说明此时正要删除左括号,就调用递归函数,last_i 和 last_j 均重置为0,括号数组初始化为反向括号。如果此时已经是反向括号了,说明之前的左括号已经删掉了变成了 ")(",然后又反转了一下,变回来了 "()",就可以直接加入结果 res 了,参见代码如下:

解法三:

  1. class Solution {
  2. public:
  3. vector<string> removeInvalidParentheses(string s) {
  4. vector<string> res;
  5. helper(s, , , {'(', ')'}, res);
  6. return res;
  7. }
  8. void helper(string s, int last_i, int last_j, vector<char> p, vector<string>& res) {
  9. int cnt = ;
  10. for (int i = last_i; i < s.size(); ++i) {
  11. if (s[i] == p[]) ++cnt;
  12. else if (s[i] == p[]) --cnt;
  13. if (cnt >= ) continue;
  14. for (int j = last_j; j <= i; ++j) {
  15. if (s[j] == p[] && (j == last_j || s[j] != s[j - ])) {
  16. helper(s.substr(, j) + s.substr(j + ), i, j, p, res);
  17. }
  18. }
  19. return;
  20. }
  21. string rev = string(s.rbegin(), s.rend());
  22. if (p[] == '(') helper(rev, , , {')', '('}, res);
  23. else res.push_back(rev);
  24. }
  25. };

下面这种解法由热心网友 fvglty 提供,应该算是一种暴力搜索的方法,并没有太多的技巧在里面,但是思路直接了当,可以作为为面试中最先提出的解法。思路是先将s放到一个 HashSet 中,然后进行该集合 cur 不为空的 while 循环,此时新建另一个集合 next,遍历之前的集合 cur,若某个字符串是合法的括号,直接加到结果 res 中,并且看若 res 不为空,则直接跳过。跳过的部分实际上是去除括号的操作,由于不知道该去掉哪个半括号,所以只要遇到半括号就都去掉,然后加入另一个集合 next 中,这里实际上保存的是下一层的候选者。当前的 cur 遍历完成后,若 res 不为空,则直接返回,因为这是当前层的合法括号,一定是移除数最少的。若 res 为空,则将 next 赋值给 cur,继续循环,参见代码如下:

解法四:

  1. class Solution {
  2. public:
  3. vector<string> removeInvalidParentheses(string s) {
  4. vector<string> res;
  5. unordered_set<string> cur{{s}};
  6. while (!cur.empty()) {
  7. unordered_set<string> next;
  8. for (auto &a : cur) {
  9. if (isValid(a)) res.push_back(a);
  10. if (!res.empty()) continue;
  11. for (int i = ; i < a.size(); ++i) {
  12. if (a[i] != '(' && a[i] != ')') continue;
  13. next.insert(a.substr(, i) + a.substr(i + ));
  14. }
  15. }
  16. if (!res.empty()) return res;
  17. cur = next;
  18. }
  19. return res;
  20. }
  21. bool isValid(string t) {
  22. int cnt = ;
  23. for (int i = ; i < t.size(); ++i) {
  24. if (t[i] == '(') ++cnt;
  25. else if (t[i] == ')' && --cnt < ) return false;
  26. }
  27. return cnt == ;
  28. }
  29. };

Github 同步地址:

https://github.com/grandyang/leetcode/issues/301

类似题目:

Different Ways to Add Parentheses

Longest Valid Parentheses

Generate Parentheses

Valid Parentheses

参考资料:

https://leetcode.com/problems/remove-invalid-parentheses/

https://leetcode.com/problems/remove-invalid-parentheses/discuss/75032/share-my-java-bfs-solution

https://leetcode.com/problems/remove-invalid-parentheses/discuss/75027/easy-short-concise-and-fast-java-dfs-3-ms-solution

https://leetcode.com/problems/remove-invalid-parentheses/discuss/75046/c-depth-limited-dfs-3ms-eliminate-duplicates-without-hashmap

LeetCode All in One 题目讲解汇总(持续更新中...)

[LeetCode] 301. Remove Invalid Parentheses 移除非法括号的更多相关文章

  1. [LeetCode] Remove Invalid Parentheses 移除非法括号

    Remove the minimum number of invalid parentheses in order to make the input string valid. Return all ...

  2. LeetCode 301. Remove Invalid Parentheses

    原题链接在这里:https://leetcode.com/problems/remove-invalid-parentheses/ 题目: Remove the minimum number of i ...

  3. [leetcode]301. Remove Invalid Parentheses 去除无效括号

    Remove the minimum number of invalid parentheses in order to make the input string valid. Return all ...

  4. 301 Remove Invalid Parentheses 删除无效的括号

    删除最小数目的无效括号,使输入的字符串有效,返回所有可能的结果.注意: 输入可能包含了除 ( 和 ) 以外的元素.示例 :"()())()" -> ["()()() ...

  5. 301. Remove Invalid Parentheses

    题目: Remove the minimum number of invalid parentheses in order to make the input string valid. Return ...

  6. [LeetCode] 301. Remove Invalid Parentheses_Hard tag:BFS

    Remove the minimum number of invalid parentheses in order to make the input string valid. Return all ...

  7. 301. Remove Invalid Parentheses去除不符合匹配规则的括号

    [抄题]: Remove the minimum number of invalid parentheses in order to make the input string valid. Retu ...

  8. 【leetcode】301. Remove Invalid Parentheses

    题目如下: 解题思路:还是这点经验,对于需要输出整个结果集的题目,对性能要求都不会太高.括号问题的解法也很简单,从头开始遍历输入字符串并对左右括号进行计数,其中出现右括号数量大于左括号数量的情况,表示 ...

  9. Leetcode之深度优先搜索(DFS)专题-301. 删除无效的括号(Remove Invalid Parentheses)

    Leetcode之深度优先搜索(DFS)专题-301. 删除无效的括号(Remove Invalid Parentheses) 删除最小数量的无效括号,使得输入的字符串有效,返回所有可能的结果. 说明 ...

随机推荐

  1. C# 消息队列之 RabbitMQ 进阶篇

    Ø  简介 在之前的 C# 消息队列之 RabbitMQ 基础入门 中介绍了 RabbitMQ 的基本用法,其实要更全面的掌握 RabbitMQ 这个消息队列服务,我们还需要掌握以下内容: 1.   ...

  2. 爬虫常用正则、re.findall 使用

    爬虫常用正则 爬虫经常用到的一些正则,这可以帮助我们更好地处理字符. 正则符 单字符 . : 除换行以外所有字符 [] :[aoe] [a-w] 匹配集合中任意一个字符 \d :数字 [0-9] \D ...

  3. vue+django分离开发的思想和跨域问题的解决

    一.介绍 在前后端分离的开发过程中,会涉及到跨域的问题,比如本次个人使用的Django+vue的框架,在vue对Django进行响应,Django再将响应的数据返回给vue,vue在进行渲染,如果不设 ...

  4. FastDFS图片服务器(分布式文件系统)学习。

    参考:https://blog.csdn.net/hiqingtian/article/details/79413471 https://blog.csdn.net/sinat_40399893/ar ...

  5. 如何查询正在运行的SQL Server agent job

    运行"msdb"系统数据库下的存储过程"dbo.sp_help_job",可以得知现在SQL Server中有多少个正在运行的agent job: USE [m ...

  6. 纯 JS 设置文本框的默认提示

    HTML5 中有个新特性叫 placeholder,一般用它来描述输入字段的预期值,适用于 text.search.password 等类型的 input 以及 textarea.示例如下: < ...

  7. 三.基础部分+asp网站搭建

    渗透测试流程:更全面地找出服务器的问题,更倾向保护 明确目标-->信息收集-->漏洞探测-->漏洞验证-->信息分析-->获取所需-->信息整理-->形成报告 ...

  8. Arthas实践--抽丝剥茧排查线上应用日志打满问题

    现象 在应用的 service_stdout.log里一直输出下面的日志,直接把磁盘打满了: 23:07:34.441 [TAIRCLIENT-1-thread-1] DEBUG io.netty.c ...

  9. centos6.8下hadoop3.1.1完全分布式安装指南

    前述:这篇文档是建立在三台虚拟机相互ping通,防火墙关闭,hosts文件修改,SSH 免密码登录,主机名修改等的基础上开始的. 一.传入文件 1.创建安装目录 mkdir /usr/local/so ...

  10. RabbitMQ启动报unknown exchange type 'x-delayed-message'

    RabbitMQ延迟队列插件未安装,导致以下问题: ShutdownSignalException: connection error; protocol method: #method<con ...