【Leetcode】301. Remove Invalid Parentheses 移除非法小括号

来源:互联网 发布:海口整站优化 编辑:程序博客网 时间:2024/06/05 14:31

题目https://leetcode.com/problems/remove-invalid-parentheses/?tab=Solutions。

题意是给出一个string,其中的小括号可能不配对,移除不配对的括号,返回所有的解。

首先,如何判断括号是否合法。可以用栈,这也是栈这个数据结构的一个典型应用。也可用一个count计数器,遇到左括号++,右括号--,一旦count小于0,就说明不合法。比较推荐count方式,空间复杂度较低。

上述方法是判定方法,那么如何通过修改得到全部解?

大致的思路肯定是一个back tracking。不过还有一些小技巧在里面,以及一些注意点。

我们还是按照判定的思路来,用一个count计数器,一旦发现count小于0,那么说明该位置多了一个“)”,就需要删除一个“)”,那么该删除哪一个位置呢?之前出现的“)”都有可能,这样肯定也会有重复,因为如果前面有连续出现的。所以对于前面连续出现的“)”,我们只删除第一个。这样就不会重复。所以这里有一个for循环用于找出所有的解。之后就要递归调用,检查后面的字符。这里要注意,一旦出现count小于0,那么里面跟一个for循环用于删除前面的“)”,for循环结束,函数就要返回,而不是继续查看后续的字符,这个工作应该递归地交给下一层函数,每一层函数只负责找第一个不符合的,如果没有就把结果输出。递归处理后续也有一个注意点,就是顺序问题,每一次删除必须在上一次删除操作位置或者后面的位置进行,否则会有顺序问题带来的重复,比如“()k))”,如果不做上述限制,一个解是“(k)”,可以先删除第一个“)”后删除第二个“)”得到,反过来也可以得到,重复解产生。可以结合下面的程序理解。

public List<String> removeInvalidParentheses(String s) {List<String> r = new ArrayList<>();    if(s == null || "".equals(s)){ r.add("");return r;}    StringBuffer sb = new StringBuffer(s);    removePa(r, sb, 0, 0, new char[]{'(', ')'});    return r;}private void removePa(List<String> r, StringBuffer sb, int start_check, int pre_delete, char[] mark){int count = 0;for(int i = start_check; i < sb.length(); i++){if(sb.charAt(i) == mark[0])count++;else if(sb.charAt(i) == mark[1])count--;if(count < 0){for(int j = pre_delete; j <= i; j++){if((sb.charAt(j) == mark[1] && j == 0) || (sb.charAt(j) == mark[1] && sb.charAt(j - 1) != mark[1])){sb.deleteCharAt(j);removePa(r, sb, i, j, mark);sb.insert(j, mark[1]);}}return;}}//if(mark[0] == '('){//removePa(r, sb.reverse(), 0, 0, new char[]{')', '('});//sb.reverse();//return;//}//r.add(sb.reverse().toString());//sb.reverse();r.add(sb.toString());return;}

以上程序只是处理了“)”,那么左括号呢?技巧是reverse一下,再递归处理,感觉这个思路实在不要太吊。但是有一个注意点,reverse以后,遇到“)”count反而要--,“(”要++,所以也不是简单的reverse。

reverse在上述程序中的位置应该是最后的reture之前,原本这里是所有的正向匹配的解得返回处,现在需要让这些解再被处理一次,而且是反向的。不过反向由于递归地存在,如果不判断会死循环,所以要有一个if。最后一个注意点,当反向也匹配时,需要返回解,由于string事先做过一次反向,所以返回时需要在做一次反向,就是r.add那里,之后还要做一次反向,这次是为了把string恢复,以便给上一层调用的下一个for的可能使用。这是backtracking的一个关键点,每一次做完一个选择,都要恢复之前的路径,给下一个选择使用。同理,当正向配对时,做了一次反向,递归调用完以后,同样要恢复一次,以便上一层调用使用。

下面是完整代码:

public List<String> removeInvalidParentheses(String s) {List<String> r = new ArrayList<>();    if(s == null || "".equals(s)){ r.add("");return r;}    StringBuffer sb = new StringBuffer(s);    removePa(r, sb, 0, 0, new char[]{'(', ')'});    return r;}private void removePa(List<String> r, StringBuffer sb, int start_check, int pre_delete, char[] mark){int count = 0;for(int i = start_check; i < sb.length(); i++){if(sb.charAt(i) == mark[0])count++;else if(sb.charAt(i) == mark[1])count--;if(count < 0){for(int j = pre_delete; j <= i; j++){if((sb.charAt(j) == mark[1] && j == 0) || (sb.charAt(j) == mark[1] && sb.charAt(j - 1) != mark[1])){sb.deleteCharAt(j);removePa(r, sb, i, j, mark);sb.insert(j, mark[1]);}}return;}}if(mark[0] == '('){removePa(r, sb.reverse(), 0, 0, new char[]{')', '('});sb.reverse();return;}r.add(sb.reverse().toString());sb.reverse();return;}

其实关于去重这个点,使用set存结果也有相同的效果,不过为了把程序写得漂亮一点,还是需要从原理上搞清楚如何去重。



0 0
原创粉丝点击