删除无效的括号

删除最小数量的无效括号,使得输入的字符串有效,返回所有可能的结果。

说明: 输入可能包含了除 ( 和 ) 以外的字符。

示例 1:

输入: "()())()"

输出: ["()()()", "(())()"]

示例 2:

输入: "(a)())()"

输出: ["(a)()()", "(a())()"]

示例 3:

输入: ")("

输出: [""]

Approach 1: Backtracking

Intuition

For this question, we are given an expression consisting of parentheses and there can be some misplaced or extra brackets in the expression that cause it to be invalid. An expression consisting of parentheses is considered valid only when every closing bracket has a corresponding opening bracket and vice versa.

This means if we start looking at each of the bracket from left to right, as soon as we encounter a closing bracket, there should be an unmatched opening bracket available to match it. Otherwise the expression would become invalid. The expression can also become invalid if the number of opening parentheses i.e. ( are more than the number of closing parentheses i.e. ).

Let us look at an invalid expression and all the possible valid expressions that can be formed from it by removing some of the brackets. There is no restriction on which parentheses we can remove. We simply have to make the expression valid.

The only condition is that we should be removing the minimum number of brackets to make an invalid expression, valid. If this condition was not present, we could potentially remove most of the brackets and come down to say 2 brackets in the end which form () and that would be a valid expression.

An important thing to observe in the above diagram is that there are multiple ways of reaching the same solution i.e. say the optimal number of parentheses to be removed to make the original expression valid is K. We can remove multiple different sets of K brackets that will eventually give us the same final expression. But, each valid expression should be recorded only once. We have to take care of this in our solution. Note that there are other possible ways of reaching one of the two valid expressions shown above. We have simply shown 3 ways each for the two valid expressions.

Coming back to our problem, the question that now arises is, how to decide which of the parentheses to remove?

Since we don't know which of the brackets can possibly be removed, we try out all the options!

For every bracket we have two choices:

  • Either it can be considered a part of the final expression OR
  • It can be ignored i.e. we can delete it from our final expression.

Such kind of problems where we have multiple options and we have no strategy or metric of deciding greedily which option to take, we try out all of the options and see which ones lead to an answer. These type of problems are perfect candidates for the programming paradigm, Recursion.

Algorithm

  • Initialize an array that will store all of our valid expressions finally.
  • Start with the leftmost bracket in the given sequence and proceed right in the recursion.
  • The state of recursion is defined by the index which we are currently processing in the original expression. Let this index be represented by the character i. Also, we have two different variables left_count and right_count that represent the number of left and right parentheses we have added to our expression till now. These are the parentheses that were considered.
  • If the current character i.e. S[i] (considering S is the expression string) is neither a closing or an opening parenthesis, then we simply add this character to our final solution string for the current recursion.
  • However, if the current character is either of the two brackets i.e. S[i] == '(' or S[i] == ')', then we have two options. We can either discard this character by marking it an invalid character or we can consider this bracket to be a part of the final expression.
  • When all of the parentheses in the original expression have been processed, we simply check if the expression represented by expr i.e. the expression formed till now is valid one or not. The way we check if the final expression is valid or not is by looking at the values in left_count and right_count. For an expression to be valid left_count == right_count. If it is indeed valid, then it could be one of our possible solutions.
    • Even though we have a valid expression, we also need to keep track of the number of removals we did to get this expression. This is done by another variable passed in recursion called rem_count.
    • Once recursion finishes we check if the current value of rem_count is < the least number of steps we took to form a valid expression till now i.e. the global minima. If this is not the case, we don't record the new expression, else we record it.

One small optimization that we can do from an implementation perspective is introducing some sort of pruning in our algorithm. Right now we simply go till the very end i.e. process all of the parentheses and when we are done processing all of them, we check if the expression we have can be considered or not.

We have to wait till the very end to decide if the expression formed in recursion is a valid expression or not. Is there a way for us to cutoff from some of the recursion paths early on because they wouldn't lead to a solution? The answer to this is Yes! The optimization is based on the following idea.

For a left bracket encountered during recursion, if we decide to consider it, then it may or may not lead to an invalid final expression. It may lead to an invalid expression eventually if there are no matching closing bracket available afterwards. But, we don't know for sure if this will happen or not.

However, for a closing bracket, if we decide to keep it as a part of our final expression (remember for every bracket we have two options, either to keep it or to remove it and recurse further) and there is no corresponding opening bracket to match it in the expression till now, then it will definitely lead to an invalid expression no matter what we do afterwards.

e.g.

( (  ) ) )

In this case the third closing bracket will make the expression invalid. No matter what comes afterwards, this will give us an invalid expression and if such a thing happens, we shouldn't recurse further and simply prune the recursion tree.

That is why, in addition to having the index in the original string/expression which we are currently processing and the expression string formed till now, we also keep track of the number of left and right parentheses. Whenever we keep a left parenthesis in the expression, we increment its counter. For a right parenthesis, we check if right_count < left_count. If this is the case then only we consider that right parenthesis and recurse further. Otherwise we don't as we know it will make the expression invalid. This simple optimization saves a lot of runtime.

Now, let us look at the implementation for this algorithm.

 import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set; class Solution { private Set<String> validExpressions = new HashSet<String>();
private int minimumRemoved; private void reset() {
this.validExpressions.clear();
this.minimumRemoved = Integer.MAX_VALUE;
} private void recurse(
String s,
int index,
int leftCount,
int rightCount,
StringBuilder expression,
int removedCount) { // If we have reached the end of string.
if (index == s.length()) { // If the current expression is valid.
if (leftCount == rightCount) { // If the current count of removed parentheses is <= the current minimum count
if (removedCount <= this.minimumRemoved) { // Convert StringBuilder to a String. This is an expensive operation.
// So we only perform this when needed.
String possibleAnswer = expression.toString(); // If the current count beats the overall minimum we have till now
if (removedCount < this.minimumRemoved) {
this.validExpressions.clear();
this.minimumRemoved = removedCount;
}
this.validExpressions.add(possibleAnswer);
}
}
} else { char currentCharacter = s.charAt(index);
int length = expression.length(); // If the current character is neither an opening bracket nor a closing one,
// simply recurse further by adding it to the expression StringBuilder
if (currentCharacter != '(' && currentCharacter != ')') {
expression.append(currentCharacter);
this.recurse(s, index + 1, leftCount, rightCount, expression, removedCount);
expression.deleteCharAt(length);
} else { // Recursion where we delete the current character and move forward
this.recurse(s, index + 1, leftCount, rightCount, expression, removedCount + 1);
expression.append(currentCharacter); // If it's an opening parenthesis, consider it and recurse
if (currentCharacter == '(') {
this.recurse(s, index + 1, leftCount + 1, rightCount, expression, removedCount);
} else if (rightCount < leftCount) {
// For a closing parenthesis, only recurse if right < left
this.recurse(s, index + 1, leftCount, rightCount + 1, expression, removedCount);
} // Undoing the append operation for other recursions.
expression.deleteCharAt(length);
}
}
} public List<String> removeInvalidParentheses(String s) { this.reset();
this.recurse(s, 0, 0, 0, new StringBuilder(), 0);
return new ArrayList(this.validExpressions);
}
}

Leetcode 301.删除无效的括号的更多相关文章

  1. Java实现 LeetCode 301 删除无效的括号

    301. 删除无效的括号 删除最小数量的无效括号,使得输入的字符串有效,返回所有可能的结果. 说明: 输入可能包含了除 ( 和 ) 以外的字符. 示例 1: 输入: "()())()&quo ...

  2. [LeetCode]301. 删除无效的括号(DFS)

    题目 题解 step1. 遍历一遍,维护left.right计数器,分别记录不合法的左括号.右括号数量. 判断不合法的方法? left维护未匹配左括号数量(增,减)(当left为0遇到右括号,则交由r ...

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

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

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

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

  5. [Swift]LeetCode301. 删除无效的括号 | Remove Invalid Parentheses

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

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

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

  7. [LeetCode] 20. Valid Parentheses 合法括号

    Given a string containing just the characters '(', ')', '{', '}', '[' and ']', determine if the inpu ...

  8. 每日一道 LeetCode (6):有效的括号

    每天 3 分钟,走上算法的逆袭之路. 前文合集 每日一道 LeetCode 前文合集 代码仓库 GitHub: https://github.com/meteor1993/LeetCode Gitee ...

  9. word中几个好用的宏代码(立方米上标、关闭样式自动更新、删除无效样式、表格加粗边框、宋体引号)

    Sub 替换立方米() With Selection.Find .Text = "m3" .Replacement.Text = "mm3" .Forward ...

随机推荐

  1. json字符串和字典类型的相互转换

    在开发过程中,有时候需要将json字符串转为字典类型,反之亦然,通常采用.Net的开源类库Newtonsoft.Json进行序列化,这里我也是采用这个,不过我更喜欢写扩展方法方便在项目的调用. 首先新 ...

  2. 动手实现 React-redux(六):React-redux 总结

    到这里大家已经掌握了 React-redux 的基本用法和概念,并且自己动手实现了一个 React-redux,我们回顾一下这几节都干了什么事情. React.js 除了状态提升以外并没有更好的办法帮 ...

  3. c#很好用的定时器Quartz--含附件

    1.引用附件中的两个DLL 2.创建类 public class QuartzJob:IStatefulJob { private static ISchedulerFactory factory = ...

  4. Spring boot Jpa添加对象字段使用数据库默认值

    Spring boot Jpa添加对象字段使用数据库默认值 jpa做持久层框架,项目中数据库字段有默认值和非空约束,这样在保存对象是必须保存一个完整的对象,但在开发中我们往往只是先保存部分特殊的字段其 ...

  5. mysql安装及基本概念

    1.mysql下载安装 在官网下载5.6版本(越老稳定性越好,现在公司一般都用5.6),选择windows,64bit .下载完解压看bin目录下是否有mysql·exe和mysqld.exe. 解压 ...

  6. GeoTools坐标转换(投影转换和仿射变换)

    GeoTools是在java下的gis开源软件,以下介绍坐标转换的两种方法:投影转换和仿射变换 投影转换 这里以xian80经纬度坐标转xian80,3度分带 111中央经线平面坐标为例 转换函数如下 ...

  7. php 生成饼状图,折线图,条形图 通用类 2

    生成饼状图,折线图,条形图通用的php类,这里使用的是国外的 HighCharts,前台页面别忘了调用HighCahrt  js HighCharts中文网站  http://www.hcharts. ...

  8. TigerGraph REST++API

    简介 - 什么是REST ++? TigerGraph TM 系统使用着名的REpresentational State Transfer(REST)架构来管理与TigerGraph核心组件,图形处理 ...

  9. ubuntu下php安装目录说明

    php当前安装目录 /etc/php5/ apache2:   采用APACHE2HANDLER启动 cli:   采用命令启动 fpm php-fpm启动 fpm2     php-fpm多实例 m ...

  10. Photoshop 注册破解

    本机测试环境为Photoshop cs4 破解方式一: 打开C:\windows\system32\drivers\etc\"找到 hosts 文件, 右键点击--打开方式---记事本,然后 ...