leetcode 32. 最长有效括号(Java版)

题目描述(题目难度,困难)

给定一个只包含 '('')' 的字符串,找出最长的包含有效括号的子串的长度。

示例 1:

输入: “(()”
输出: 2
解释: 最长有效括号子串为 “()”

示例 2:

输入: “)()())”
输出: 4
解释: 最长有效括号子串为 “()()”

题目求解

解法一

括号字符串依次入栈,删除匹配的成对括号。最后栈中留下的都是无法匹配的断点。这些断点的差值减一就是断点间有效括号串的长度,取这些长度的最大值即可。
例如括号字符串 “)()((())(”,最后留在栈中的字符下标为 0 3 8。根据这三个断点可以得到,两个有效括号子串的长度分别为 3-0-1 = 2 和 8-3-1=4。所以最长的长度就是 4。

public int longestValidParentheses(String s) {
	if(s.length() <= 1) return 0;
	List<Integer> stack = new ArrayList<>();
	for(int i = 0; i < s.length(); ++i){
		if(!stack.isEmpty() 
				&& s.charAt(i) == ')' 
				&& s.charAt(stack.get(stack.size()-1)) == '(') stack.remove(stack.size()-1);
		else stack.add(i);
	}
	stack.add(s.length());
	int longest = stack.get(0);
	int diff;
	for(int i = 1; i < stack.size(); ++i){
		if((diff = stack.get(i)-stack.get(i-1)-1) > longest) longest = diff;
	}
    return longest;
}

解法二

同样是借助栈,遍历一遍 s 字符串,就可以获得结果。
与解法一不同的是,解法二只有左括号下标能进栈,右括号下标不会进栈。
在左括号下标出栈时,同时更新最长的有效括号子串长度,比解法一少了后面作差比较的环节。
start 记录的是不可能与之前括号串连成有效括号串的下界,更新 longest 分为两种情况:

  1. 栈空,说明当前最长的有效括号子串是有若干段有效括号子串连接而成的。所以长度要从下界开始算起。对应这种情况 ‘)()(())’,最长的有效长度子串 ‘()(())’,是由 ‘()’、’(())’ 两个有效括号子串连接起来的,所以要以第一个 ‘)’ 为界,计算长度。
  2. 栈非空,栈中还有 ‘(’,说明当前有效括号子串还没能和前一个有效括号子串连起来,所以只用当前有效括号子串长度来更新。对应这种情况 ‘()((())’,栈中有多余的 ‘(’,表示两个有效括号子串还没连起来,应该分开计算。
int longestValidParentheses(String s) {
    int longest = 0, start = 0;
    Deque<Integer> stack = new ArrayDeque<>();
    for(int i = 0; i < s.length(); ++i){
        char c = s.charAt(i);
        if(c == '(') stack.push(i);
        else if(c == ')'){
            if(stack.isEmpty()) start = i + 1;
            else{
                stack.pop();
                int t;
                if(stack.isEmpty()){
                	if((t = i-start+1) > longest) longest = t;
                }else if((t = i-stack.peek()) > longest) longest = t;
            }
        }
    }
    return longest;
}

解法三:

解法三是一种没有用到栈,效率很高的算法。
算法分别从前到后和从后到前两次遍历 s 括号字符串,两次遍历结果的较大值,就是 s 括号字符串中最长有效括号子串的长度。
以从前到后遍历为例,算法使用一个 count 计数器,来指示当前有效括号子串是否肯定结束。
count 计数器的计数流程是如果当前字符是 ‘(’,count 计数器就自加,如果当前字符是 ‘)’,count 计数器就自减。
如果 count 小于 0,说明 ‘)’ 数量已经超过了 ‘(’ 的数量,就可以肯定的说上一个有效括号子串已经结束,更新最长有效括号子串长度。重置变量,开始下一轮计数。
但如果 count 大于 0,则无法说明当前有效括号子串结束。
也就是说如果遍历结束时,count 变量大于 0,则无法确定最后一轮计数中,有效括号子串的长度。
这个算法理解的难点在于,我们要清楚地知道有效括号子串结束的条件,有两种,一种是右括号数量大于左括号数量,另一种是在左括号数量还大于右括号数量时字符串结束,这导致了有效括号子串被动的结束。
此算法只考虑了第一种情况,没考虑第二种情况,但再做一次反向遍历,就可以解决这个问题,这也是此算法的巧妙之处。

public int longestValidParentheses(String s) {
	int a = getLen(s, 1, '('); // 从前到后遍历
	int b = getLen(s, -1, ')'); // 从后到前遍历
	return Math.max(a, b);
}
private int getLen(String s , int d, char c){
	int start = 0, end = s.length();
	if(d == -1){
		start = s.length()-1;
		end = -1;
	}
	int max = 0, count = 0, len = 0;
	for(int i = start;i != end; i += d){
		count += (s.charAt(i) == c ? 1 : -1);
        ++len;
		if(count < 0){
			count = 0;
			len = 0;
		}else if(count == 0){
			if(len > max) max = len;
        }
	}
	return max;
}

那么既然知道了只有当 count > 0 时,才需要反向遍历,则我们可以稍微优化代码如下。

public int longestValidParentheses(String s) {
    int count = 0, len = 0, max = 0;
    for(int i = 0; i < s.length(); ++i){
        if(s.charAt(i) == '(') ++count;
        else --count;
        ++len;
        if(count < 0) count = len = 0;
        else if(count == 0 && len > max) max = len;
    }
    if(count > 0){
        count = len = 0;
        for(int i = s.length()-1; i >= 0; --i){
            if(s.charAt(i) == ')') ++count;
            else --count;
            ++len;
            if(count < 0) count = len = 0;
            else if(count == 0 && len > max) max = len;
        }
    }
    return max;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章