1. 程式人生 > >最長合法括號

最長合法括號

Longest Valid Parentheses 最長合法括號

一、問題描述

給一個只由’(‘和’)'組成的字串,找出其中最長的連續合法子串長度。(來源Leetcode)

樣例

Input:(()
Output:2

Input: )()())
Output: 4

Input: ((())
Output: 4

二、解決方案

很明顯,可以用動態規劃,我也是這麼想的。

1. 我的方案

構建dp[len][len], dp[i][j]表示以i開頭,j結尾的子串中最長子串。s表示字串,然後給出遞推式
d

p [ i ] [ j ] = {
2 , if i+1 = j and s[i] = ’(’ and s[j] = ’)’
d p [ i + 1 ] [ j 1 ] + 2 , if dp[i+1][j-1] > 0 and s[i] = ’(’ and s[j] = ’)’ d p [ k + 1 ] [ j ] + d p [ i ] [ k ] , if dp[i][k] > 0 and dp[k+1][j] > 0,  k ( i , j )
dp[i][j] = \begin {cases} 2, & \text{if i+1 = j and s[i] = '(' and s[j] = ')'}\\ dp[i+1][j-1] + 2, & \text{if dp[i+1][j-1] > 0 and s[i] = '(' and s[j] = ')'}\\ dp[k+1][j] + dp[i][k], & \text{if dp[i][k] > 0 and dp[k+1][j] > 0, } k \in (i, j)\\ \end {cases}
注意,迴圈的時候注意 i ( l e n , 0 ] , j [ i + 1 , l e n ) , k ( i , j ) i \in (len, 0], j \in[i+1, len), k \in (i, j)

class Solution {
public:
    int longestValidParentheses(string s) {
        int len = s.size();
        vector<vector<int> > dp(len, vector<int>(len, 0));
        int max = 0;
        for(int i = len - 1; i >= 0; -- i){
            for(int j = i + 1; j < len; j += 2){
                if(i + 1 == j && s[i] == '(' && s[j] == ')')
                    dp[i][j] = 2;
                else if(dp[i+1][j-1] > 0 && s[i] == '(' && s[j] == ')')
                    dp[i][j] = dp[i+1][j-1] + 2;
                else{
                    for(int k = i + 1; k < j; k += 2){
                        if(dp[i][k] > 0 && dp[k+1][j] > 0){
                            dp[i][j] = dp[k+1][j] + dp[i][k];
                            break;
                        }
                    }
                }
                if(max < dp[i][j])
                    max = dp[i][j];
            }
        }
        return max;
    }
};

結果分析,雖說用了動態規劃的思想,但是明顯時間和空間複雜度過高,儘管有一定技巧性,但本質上是 O ( n 3 ) , O ( n 2 ) O(n^3), O(n^2) , 還不如暴力搜尋,至少暴力搜尋的空間複雜度是 O ( n ) O(n)

2. Leetcode上的解決方案

原文連結,以下內容主要是來源於LeetCode上的解決方案,我主要是翻譯了一下,主要分為暴力,動態規劃,堆疊,無需額外空間法。

2.1暴力(Brute Force)

找出所有偶數子串,然後使用堆疊判斷是否是合法括號。
時間複雜度和空間複雜度為 O ( n 3 ) , O ( n ) O(n^3), O(n)

public class Solution {
    public boolean isValid(String s) {
        Stack<Character> stack = new Stack<Character>();
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == '(') {
                stack.push('(');
            } else if (!stack.empty() && stack.peek() == '(') {
                stack.pop();
            } else {
                return false;
            }
        }
        return stack.empty();
    }
    public int longestValidParentheses(String s) {
        int maxlen = 0;
        for (int i = 0; i < s.length(); i++) {
            for (int j = i + 2; j <= s.length(); j+=2) {
                if (isValid(s.substring(i, j))) {
                    maxlen = Math.max(maxlen, j - i);
                }
            }
        }
        return maxlen;
    }
}

2.2 動態規劃(Dynamic Programming)

只需一個一維動態陣列,dp[i]表示,以第i個字元結尾的合法子串長度。換句話說,合法子串包括第i個字元。時間複雜度和空間複雜度分別為 O ( n ) , O ( n ) O(n), O(n) 。下面給出遞推式
d p [ i ] = { d p [ i 2 ] + 2 , if i is even and s[i-1] = ’(’ and s[i] = ’)’ d p [ i 1 ] + d p [ i d p [ i 1 ] 2 ] + 2 , if i is even and s[i-1] = ’)’ and s[i] = ’)’ and s[i−dp[i−1]−1]=’(’ 0 , others dp[i] = \begin {cases} dp[i-2] + 2, &amp; \text{if i is even and s[i-1] = &#x27;(&#x27; and s[i] = &#x27;)&#x27;}\\ dp[i-1] + dp[i - dp[i-1] - 2] + 2, &amp; \text{if i is even and s[i-1] = &#x27;)&#x27; and s[i] = &#x27;)&#x27; and s[i−dp[i−1]−1]=&#x27;(&#x27;}\\ 0, &amp; \text{others}\\ \end {cases}

public class Solution {
    public int longestValidParentheses(String s) {
        int maxans = 0;
        int dp[] = new int[s.length()];
        for (int i = 1; i < s.length(); i++) {
            if (s.charAt(i) == ')') {
                if (s.charAt(i - 1) == '(') {
                    dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2;
                } else if (i - dp[i - 1] > 0 && s.charAt(i - dp[i - 1] - 1) == '(') {
                    dp[i] = dp[i - 1] + ((i - dp[i - 1]) >= 2 ? dp[i - dp[i - 1] - 2] : 0) + 2;
                }
                maxans = Math.max(maxans, dp[i]);
            }
        }
        return maxans;
    }
}

2.3 棧(Using Stack)

新建一個Stack,

  1. 壓入 -1;
  2. 對每個字元,如果是’(’,壓入該索引,
  3. 如果是’)’, 彈出棧頂元素,此時用該索引,減去彈出元素之後的棧頂元素,即為當前合法子串長度。如果棧為空,則將’)'的索引壓入棧中。
    該演算法的時間複雜度和空間複雜度為 O ( n ) , O ( n ) O(n), O(n)
public class Solution {

    public int longestValidParentheses(String s) {
        int maxans = 0;
        Stack<Integer> stack = new Stack<>();
        stack.push(-1);
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == '(') {
                stack.push(i);
            } else {
                stack.pop();
                if (stack.empty()) {
                    stack.push(i);
                } else {
                    maxans = Math.max(maxans, i - stack.peek());
                }
            }
        }
        return maxans;
    }
}

2.4 無需額外空間(Without extra space)

用兩個變數left和right儲存左右括號數量,當left == right,表示當前最大子串長度,當right > left,表示遇到不合法,兩個變數置0。當遇到"(()",此時該方法失效,因此從左往右掃,然後從右往左掃,即可完美解決所有情況。
該演算法的時間複雜度和空間複雜度為 O ( n ) , O ( 1 ) O(n), O(1)

public class Solution {
    public int longestValidParentheses(String s) {
        int left = 0, right = 0, maxlength = 0;
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == '(') {
                left++;
            } else {
                right++;
            }
            if (left == right) {
                maxlength = Math.max(maxlength, 2 * right);
            } else if (right >= left) {
                left = right = 0;
            }
        }
        left = right = 0;
        for (int i = s.length() - 1; i >= 0; i--) {
            if (s.charAt(i) == '(') {
                left++;
            } else {
                right++;
            }
            if (left == right) {
                maxlength = Math.max(maxlength, 2 * left);
            } else if (left >= right) {
                left = right = 0;
            }
        }
        return maxlength;
    }
}