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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章