[leetcode] 32. 最长有效括号

题目描述

给你一个只包含 '('')' 的字符串,找出最长有效(格式正确且连续)括号子串的长度。

示例 1:

输入:s = "(()"
输出:2
解释:最长有效括号子串是 "()"

示例 2:

输入:s = ")()())"
输出:4
解释:最长有效括号子串是 "()()"

示例 3:

输入:s = ""
输出:0

提示:

  • 0 <= s.length <= 3 * 104
  • s[i] 为 ‘(’ 或 ‘)’

解题方法

方法一:栈

利用栈先进后出的特性,每次匹配到'('字符,将'('字符的下标位置入栈;当匹配到')' 字符时,将栈顶一个'('字符的下标位置出栈,出栈的'('字符就是对应')'字符匹配的左括号。设匹配到')' 字符下标位置为j,对应出栈'('字符下标位置为ij - i + 1即为当前的’)'字符匹配的括号长度。

通过上面这种方式我们能求出所有’)'对应'('的括号长度,取其中的最大值就是最长有效括号子串的长度吗?答案是否定的。还有一种情况,虽然匹配到了当前')'匹配到了'(',但是与匹配的'('相邻的前面字符串中也可能存在有效的括号子串。此时,就需要将前面子串的括号长度相加。

举个例子,设字符串为" ( ) ( ) \color {red}()\color {green}() ()()",此时绿色的括号匹配长度是2,但我们还需要加上前面红色括号的匹配长度,才能求出最长有效括号子串的长度。

友情提示:以下是正确思路

那我们需要怎么做呢?其实也很简单,只需要提供一个变量start,用来记录有效括号子串的起始位置。初始栈为空时,加入的第一个'('的下标即为有效括号子串的起始位置。而当栈中没有'(',而又匹配到')'时,原来的start位置废弃,后面加入的第一个'('的下标即为新的start。那么当匹配到')'时,如何计算最长有效括号子串的长度呢?分以下两种情况讨论。

  • 第一种,当匹配到的字符')'有对应出栈'('字符,且'('出栈后栈不为空。设当前')'下标为j'('出栈后栈顶元素为i,则当前')'匹配的有效括号子串的长度为j - i
  • 第二种,当匹配到的字符')'有对应出栈'('字符,且'('出栈后栈为空。设当前')'下标为j,则当前')'匹配的有效括号子串的长度为j - start + 1

取上面两种情况最大值即为最长有效括号子串的长度。

java代码

public int longestValidParentheses(String s) {
    if (s == null || s.length() == 0) {
        return 0;
    }
    // 记录有效子串的起始位置
    int start = 0;
    int max = 0;
    Stack<Integer> stack = new Stack<Integer>();
    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();
                if (stack.isEmpty()) {
                    // 栈为空时,有效子串的长度 = 右括号的位置 - 有效子串的起始位置 + 1
                    max = Math.max(max, i - start + 1);
                } else {
                    // 栈不为空时,有效子串的长度 = 右括号的位置 - 栈顶元素的位置,因为栈顶元素的下一个位置是匹配的'('
                    max = Math.max(max, i - stack.peek());
                }
            }
        }

    }
    return max;
}

复杂度分析

时间复杂度: O ( N ) O(N) O(N) N N N为字符串长度。需要遍历一次字符串。
空间复杂度: O ( N ) O(N) O(N),需要提供栈的存储空间。

方法二:贪心

方法一需要用栈存储左括号位置,那么有没有一种方法只使用 O ( 1 ) O(1) O(1)的常数空间呢?答案是有的,不过需要遍历两次字符串,我说一下思路。

第一次遍历,我们需要从头到尾遍历字符串,我们记 l e f t left left为匹配的'('数量, r i g h t right right为匹配的')'数量数量。当 r i g h t > l e f t right>left right>left时, l e f t left left r i g h t right right置为 0 0 0;当 r i g h t = = l e f t right==left right==left时,计算当前字符串的有效长度为 2 × r i g h t 2 \times right 2×right,并且记录目前为止最长字符串长度。此次遍历,有一种情况没有考虑到。例如字符串" ( ( ) (() (()",当 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置为 0 0 0;当 l e f t = = r i g h t left==right left==right时,计算当前字符串的有效长度为 2 × l e f t 2 \times left 2×left,并且记录目前为止最长字符串长度。

java代码

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

复杂度分析

时间复杂度: O ( N ) O(N) O(N) N N N为字符串长度。需要遍历两次字符串。
空间复杂度: O ( 1 ) O(1) O(1),只需要存储常数级别的变量。

相似题目

[leetcode] 20. 有效的括号


  • 个人公众号
    个人公众号
  • 个人小游戏
    个人小游戏
  • 30
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

会飞的大鱼人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值