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

题目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存结果也有相同的效果,不过为了把程序写得漂亮一点,还是需要从原理上搞清楚如何去重。



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值