【LeetCode】解题32:Longest Valid Parentheses(多解法:栈+动态规划+计数器)

LeetCode解题 32:Longest Valid Parentheses(多解法:栈+动态规划+计数器)

Problem 32: Longest Valid Parentheses [Hard]

Given a string containing just the characters '(' and ')', find the length of the longest valid (well-formed) parentheses substring.

Example 1:

Input: “(()”
Output: 2
Explanation: The longest valid parentheses substring is “()”

Example 2:

Input: “)()())”
Output: 4
Explanation: The longest valid parentheses substring is “()()”

来源:LeetCode

解题思路

1. 栈

维护一个栈,起始压入-1,然后不停压入'('')'的下标。当')'遇到前面是'('时就消去,并计算有效长度;当')'前面没有'('的时候,说明无效,需要清空并将')'的下标压入最底层,用于之后计算有效长度。

具体思路:

  • 创建栈sk,初始化压入-1。

  • 遍历字符串s,遇到'('时直接压入,遇到'('时需要分情况讨论:
    i. 如果栈顶是'(',将其pop()后计算当前有效长度并更新maxLen:
    m a x L e n = max ⁡ { m a x L e n , i − s k . p e e k ( ) } maxLen = \max\{maxLen, i - sk.peek()\} maxLen=max{maxLen,isk.peek()}
    其中,i是当前遍历至的下标,sk.peek()是栈中消去的'('的前一个位置的下标。

    ii. 如果栈顶不是'(',那么一定是-1或者')',并且栈中一定只剩下了一个元素(因为之前所有的')'都会被消去,只剩下一个用于计算有效长度)。此时需要将之前剩余的元素pop(),并将当前的')'下标压入栈,用于后续计算长度。

时间复杂度为 O ( n ) O(n) O(n),空间复杂度最坏情况下为 O ( n ) O(n) O(n)

运行结果:
在这里插入图片描述

2. 动态规划

使用动态规划思想。维护数组valLen[N],valLen[i]代表以字符串中s[i]结尾的最大有效长度。当s[i] = '('时,valLen[i]一定为零;当s[i] = ')'时,需要同时考虑s[i-1]。

遍历s,valLen[i]根据s[i]和s[i-1]的情况可以由valLen[i-1]、valLen[i-2]、valLen[i-valLen[i-1]-2]等前项转换而来,具体转换公式为:

  1. 当s[i] = '(' 时, v a l L e n [ i ] = 0 valLen[i] = 0 valLen[i]=0

  2. 当s[i] = ')'时,需要考虑前一个字符,即s[i-1]。

    2.1 若s[i-1] = '(',也就是字符串为###()$,则有效长度应为以s[i-2]结尾的最大有效长度再加2,转换公式为:
    v a l L e n [ i ] = v a l L e n [ i − 2 ] valLen[i] = valLen[i-2] valLen[i]=valLen[i2]
    2.2 若s[i-1] = ')',也就是字符串为###)),则应该往前推valLen[i-1]个位置,看i-valLen[i-1]-1位置的字符是不是'(',也就是看##x...))中x位置,其中...)的长度就是valLen[i-1],是一个有效字符串。

    如果x = ')',也就不能与s[i]的')'匹配上,则 v a l L e n [ i ] = 0 valLen[i] = 0 valLen[i]=0

    如果x = '(',则字符串为##(...)),其中...)的长度为valLen[i-1],还要加上新匹配上的两个括号,同时还要看x的前一个字符的最大有效长度(前一个字符为i - valLen[i-1] - 2),因此转换公式为:
    v a l L e n [ i ] = v a l L e n [ i − 1 ] + 2 + v a l L e n [ i − v a l L e n [ i − 1 ] − 2 ] valLen[i] = valLen[i-1] + 2 + valLen[i-valLen[i-1]-2] valLen[i]=valLen[i1]+2+valLen[ivalLen[i1]2]

  3. 最后更新maxLen:
    m a x L e n = max ⁡ { m a x L e n , v a l L e n [ i ] } maxLen = \max\{maxLen, valLen[i]\} maxLen=max{maxLen,valLen[i]}

时间复杂度为 O ( n ) O(n) O(n),空间复杂度为 O ( n ) O(n) O(n)

运行结果:
在这里插入图片描述

3. 计数器

创建两个计数器 l e f t left left r i g h t right right用于记录左括号和右括号的个数。

第一次从左到右遍历:

  • 遇到'('时, l e f t left left++,遇到)时, r i g h t right right++。
  • l e f t < r i g h t left < right left<right时,说明计数无效, l e f t left left r i g h t right right都清零。
  • l e f t = r i g h t left = right left=right时,说明括号可以完全匹配,更新maxLen为:
    m a x L e n = max ⁡ { m a x L e n , l e f t + r i g h t } 。 maxLen = \max\{maxLen, left + right\}。 maxLen=max{maxLen,left+right}
    如果遍历结束后发现 l e f t > r i g h t left > right left>right说明中间可能有能够匹配上的部分因为左括号较多而无法满足 l e f t = r i g h t left = right left=right,因此需要反向遍历一次。

第二次从右到左遍历:

  • 遇到'('时, l e f t left left++,遇到)时, r i g h t right right++。
  • l e f t > r i g h t left > right left>right时,说明计数无效, l e f t left left r i g h t right right都清零。
  • l e f t = r i g h t left = right left=right时,说明括号可以完全匹配,更新maxLen为:
    m a x L e n = max ⁡ { m a x L e n , l e f t + r i g h t } 。 maxLen = \max\{maxLen, left + right\}。 maxLen=max{maxLen,left+right}

时间复杂度为 O ( n ) O(n) O(n),空间复杂度为 O ( 1 ) O(1) O(1)

运行结果:
在这里插入图片描述

要点:动态规划

Solution (Java)

1. 栈

class Solution {
    public int longestValidParentheses(String s) {
        int N = s.length();
        if(N < 2) return 0;
        int max = 0;
        Stack<Integer> sk = new Stack<Integer>();
        sk.push(-1);
        for(int i = 0; i < N; i++){
            if(s.charAt(i) == '('){
                sk.push(i);
            }
            else{
            	// opt1
                /*if(sk.size() == 1){
                    sk.pop();
                    sk.push(i);
                }
                else{ // sk.peek() = '('
                    sk.pop();
                    max = Math.max(max, i - sk.peek());
                }*/
                // opt2[*]
                sk.pop();
                if(sk.empty()) sk.push(i);
                else{ // sk.pop() = '('
                    max = Math.max(max, i - sk.peek());
                }
            }
        }
        return max;
    }
}

2. 动态规划

class Solution {
    public int longestValidParentheses(String s) {
        int N = s.length();
        if(N < 2) return 0;
        int max = 0;
        int len;
        int[] valLen = new int[N];
        valLen[0] = 0;
        for(int i = 1; i < N; i++){
            if(s.charAt(i) == '(') valLen[i] = 0;
            else{ // s[i] = ')'
                if(s.charAt(i-1) == '('){
                    len = 2;
                    if(i-2 > 0) len += valLen[i-2];
                    valLen[i] = len;
                    max = Math.max(max, len);
                }
                else{
                    len = valLen[i-1];
                    if(i-len-1 < 0 || s.charAt(i-len-1) == ')') len = 0;
                    else{
                        if(i-len-2 < 0) len += 2;
                        else len += valLen[i-len-2] + 2;
                    }
                    valLen[i] = len;
                    max = Math.max(max, len);
                }
            }
        }
        return max;
    }
}

3. 计数器

class Solution {
    public int longestValidParentheses(String s) {
        int N = s.length();
        if(N < 2) return 0;
        int max = 0;
        int left = 0; // num of '('
        int right = 0; // num of ')'
        // from s[0] to s[N-1]
        for(int i = 0; i < N; i++){
            if(s.charAt(i) == '(') left++;
            else right++;
            if(left < right){
                left = 0;
                right = 0;
            }
            else if(left == right){
                max = Math.max(max, left+right);
            }
        }
        if(left < right) return max;
        left = 0;
        right = 0;
        // from s[N-1] to s[0]
        for(int i = N-1; i >= 0; i--){
            if(s.charAt(i) == ')') right++;
            else left++;
            if(left > right){
                left = 0;
                right = 0;
            }
            else if(left == right){
                max = Math.max(max, left+right);
            }
        }
        return max;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值