032J. Longest Valid Parentheses

https://leetcode.com/problems/longest-valid-parentheses/

在一段合法的子数组中,左右括号数目首先要相同,并且,
左括号数量在任何时候不得少于右括号。
所以唯一会导致长度停止计算的就是如下
以(开头:
(/无 + 最长合法的子数组 + ( / 无
以)开头
)/无 + 最长合法的子数组 + ) /无

那么无论用什么方法,都需要处理这个情况。

Method 1: Stack

class Solution {
    public int longestValidParentheses(String s) {
        int ans = 0;
        Stack<Integer> stack = new Stack<>();
        stack.push(-1); // dummyhead
        for(int i = 0; i < s.length(); i++){
            char c = s.charAt(i);
            if( c == '('){
                stack.push(i);
            }else{
                stack.pop();
                if(stack.isEmpty()){
                    //如果pop完栈空了
                    //这里对应的情况是有更多的右括号不再valid了 比如 ))))() 
                    //此时我们直接更新dummyhead的位置。
                    stack.push(i);
                }else{
                    ans = Math.max(ans,i - stack.peek());                    
                }       
            }
        }
        
        return ans;
    }
}

Method 2: 动态规划

dp数组是用来储存当前的连续的最长子数组的。
一个正确的子数组,应当以')'结尾。因此碰到左括号'('直接标记0.
如果')'之前
1. 是 ‘(’:
   那么应该有一个暂时的长度为2的合法子数组。
   此时我们还应该它上一个合法的位置是不是也是合法的子数组,如果是的话就加2。
   因此有 dp[i] = (i > 1 ? dp[i-2] : 0) + 2;
2. 是 ‘)’:
   我们就找到最后一个合法的位置之前来判断。
   比如如果i-1处的右括号的dp值是4,那么就意味着在 i - 4 - 1 处的括号需要我们去判断,如果
   它是左括号,应当再 + 2,不然该位就是0;
   因此定义 lastPos = i - dp[i-1] - 1。
   dp[i] = dp[i-1] + 2 + (lastPos > 1 ? dp[lastPos - 1] : 0) ;

综合并简化:
   我们发现
   dp[i] = (i > 1 ? dp[i-2] : 0) + 2; 是
   dp[i] = dp[i-1] + 2 + (lastPos > 1 ? dp[lastPos - 1] : 0) ;
   的特殊情况,因为上面1情况时,dp[i-1] = 0, 而且 lastPos = i - 1。
   所以我们可以把代码写成更紧凑的形式。
   即 总结如下:
   i 的字符是右括号时,如果 lastPos(即:最近一个需要被判断的位置) = i - dp[i-1] - 1, 不为负且该位置是左括号'('时,
    dp[i] = dp[i-1] + 2 + (lastPos > 1 ? dp[lastPos - 1] : 0); 
    ans = Math.max(ans, dp[i]);  
class Solution {
    public int longestValidParentheses(String s) {
        if(s.length() < 2) return 0;
        int ans = 0;
        int[] dp = new int[s.length()];
        dp[0] = 0;
        for(int i = 1; i < s.length(); i++){
            char lastc = s.charAt(i-1);
            char c = s.charAt(i);
            if(c == '('){ //碰到左括号直接标记0
               dp[i] = 0; 
            }else{ //碰到右括号
                // 如果前一位是左括号,说明配对成功。
                if(lastc == '('){
                    dp[i] = (i > 1 ? dp[i-2] : 0) + 2;
                }else{
                    int lastPos = i - dp[i-1] - 1;
                    if(lastPos >= 0 && s.charAt(lastPos) == '('){
                        dp[i] = dp[i-1] + 2 + (lastPos > 1 ? dp[lastPos - 1] : 0) ;
                    }  
                } 
                ans = Math.max(ans, dp[i]);
            }
        }
        return ans;
    }
}

以上代码可以被简化成如下

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

Method 神奇

这个方法是将String 从左到右 再 从右到左扫描一遍。
统计左括号的数量left,和右括号的数量right.
先从左往右扫描:
当他们数量相等的时候就记录下此时的长度。
如果右的超过了左的,说明一定不合法了。那么从头开始计数 left, right都归0.
再从右往左扫:
此时左括号和右括号的性质完全相反了。
核心的问题是为什么这个方法有效呢?
我们可以做一个积分,每一个左括号是1,右括号是-1。
下图中 黑线 是从左往右 红线是从右往左。线的高度就是积分值。
从左往右的时候, 黑线一旦小于0 (right > left),就一直等于0.
从右往左的时候,红线一旦大于0 (left > right) 就一直等于0。
可以看出,这样以来就把所有隐藏的位置都找到了。

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;
    }
}

运用上面的想法,在2023年写的答案:

class Solution {
    public int longestValidParentheses(String s) {
        int longest = 0;
        int curScore = 0;
        int curLength = 0;
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == '(') {
                curScore += 1;
            } else {
                curScore -= 1;
            }
            if (curScore < 0) {
                curScore = 0;
                curLength = 0;
            } else {
                curLength++;
                if (curScore == 0) {
                    longest = Math.max(longest, curLength);
                }
            }

        }
        curScore = 0;
        curLength = 0;
        for (int i = s.length() - 1; i >= 0; i--) {
            if (s.charAt(i) == ')') {
                curScore += 1;
            } else {
                curScore -= 1;
            }
            if (curScore < 0) {
                curScore = 0;
                curLength = 0;
            } else {
                curLength++;
                if (curScore == 0) {
                    longest = Math.max(longest, curLength);
                }
            }
        }
        return longest;
    }
}

Last updated

Was this helpful?