LeetCode 32. 最长有效括号

题目描述

给你一个只包含 '('')' 的字符串,找出最长有效括号子串的长度。

有效括号要求左右括号正确匹配,并且子串必须是连续的。

例如:

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

最初思路

一开始想到的是双指针:

复制代码
让 l 指向合法括号区间里的 '('
让 r 指向可以匹配的 ')'

但实际写的时候会发现,l 怎么移动并不好处理。因为有效括号不仅要求数量匹配,还要求中间的结构也匹配,比如 ")()""())()" 这类情况会打断连续区间。

后面改成用栈记录左括号下标,匹配成功后标记有效位置,再统计最长连续有效区间。

问题出在哪里

第一版栈写法中,遇到 ')' 时直接执行匹配:

复制代码
isValid[i] = isValid[st[top--]] = true

但如果当前右括号前面没有可以匹配的左括号,top 就是 -1

例如:

复制代码
s = ")()"

第一轮遍历时:

复制代码
i = 0
s[i] = ')'
top = -1

这时访问 st[top] 就会访问非法下标。

所以遇到 ')' 时,必须先判断栈里是否还有未匹配的 '('

正确思路

这个解法分成两步:

复制代码
1. 用栈找出所有能成功匹配的括号位置
2. 扫描标记数组,统计最长连续 true 的长度

栈里保存的是还没有被匹配的 '(' 的下标。

遍历字符串时:

复制代码
遇到 '(':
    把当前下标入栈

遇到 ')':
    如果栈非空,说明可以和最近的一个 '(' 匹配
    把当前 ')' 和栈顶 '(' 的位置都标记成有效
    然后弹出这个 '('

最后,所有被标记为 true 的位置都属于某个有效括号片段。最长有效括号子串,就是最长连续 true 的长度。

关键不变量

栈中的下标始终表示:

复制代码
已经遍历过,但还没有被匹配的 '(' 的下标。

因此,当遇到一个 ')'top >= 0 时:

复制代码
st[top] 就是当前 ')' 能匹配到的最近一个未匹配 '(' 的下标。

匹配成功后执行 top--,这个 '(' 就从栈里弹出,后面不会再被别的 ')' 重复匹配。

手推过程

以这个字符串为例:

复制代码
s = "())()"

遍历过程:

复制代码
i = 0, '(' 入栈,栈中有 [0]
i = 1, ')' 匹配 st[top] = 0,标记 0 和 1
i = 2, ')' 但栈空,无法匹配
i = 3, '(' 入栈,栈中有 [3]
i = 4, ')' 匹配 st[top] = 3,标记 3 和 4

标记数组可以理解为:

复制代码
下标: 0 1 2 3 4
字符: ( ) ) ( )
标记: T T F T T

最长连续 true 的长度是 2,所以答案是 2

边界处理

需要特别注意两类边界:

复制代码
1. 当前字符是 ')',但栈为空:不能匹配,直接跳过
2. 字符串为空,或者没有任何有效括号:答案应该是 0

所以 ans 初始值应该设为 0,而不是 -1

伪代码

下面的伪代码与后面的 Java 实现一一对应:

复制代码
n = 字符串长度
创建数组 st 作为栈,保存 '(' 的下标
top = -1
创建布尔数组 isValid,标记每个位置是否属于有效括号

遍历字符串中的每个下标 i:
    如果 s[i] 是 '(':
        top 加 1
        st[top] = i
    否则如果 top >= 0:
        把 i 标记为有效
        把 st[top] 标记为有效
        top 减 1

ans = 0
cnt = 0
遍历 isValid:
    如果当前位置为 true:
        cnt 加 1
        用 cnt 更新 ans
    否则:
        cnt = 0

返回 ans

Java 代码

复制代码
class Solution {
    public int longestValidParentheses(String s) {
        int n = s.length();
        int[] st = new int[n];
        int top = -1;
        boolean[] isValid = new boolean[n];
        char[] c = s.toCharArray();

        for (int i = 0; i < n; i++) {
            if (c[i] == '(') {
                st[++top] = i;
            } else if (top >= 0) {
                isValid[i] = isValid[st[top--]] = true;
            }
        }

        int ans = 0;
        int cnt = 0;
        for (boolean v : isValid) {
            if (v) {
                cnt++;
                ans = Math.max(ans, cnt);
            } else {
                cnt = 0;
            }
        }

        return ans;
    }
}

易错点

1. 空栈时不能匹配右括号

遇到 ')' 时,不能直接访问 st[top]

必须先判断:

复制代码
top >= 0

否则像 ")()" 这种以右括号开头的字符串,会在第一轮就访问非法下标。

2. st[top] 保存的是尚未匹配的左括号

st[top] 不是任意一个左括号,而是最近的、还没有被匹配的 '(' 下标。

匹配成功后:

复制代码
top--

这个左括号就已经被使用,后面不能再匹配。

3. 最长的是连续有效区间

标记为 true 的位置必须连续,才属于同一个有效括号子串。

例如:

复制代码
s = "())()"
标记:T T F T T

虽然一共有四个位置被标记为有效,但中间被 F 断开,所以最长长度是 2,不是 4

4. 答案初始值应该是 0

如果字符串为空,或者没有任何有效括号,比如:

复制代码
s = ""
s = ")))"

答案都应该是 0

建议测试用例

复制代码
s = ")()"
输出:2

s = "())()"
输出:2

s = ")()())"
输出:4

s = ""
输出:0

s = ")))"
输出:0

复杂度分析

  • 时间复杂度:O(n),一次遍历字符串做匹配,一次遍历标记数组统计最长连续有效区间。
  • 空间复杂度:O(n),使用了栈数组和 isValid 标记数组。

总结

这题的关键是先把"括号是否匹配"和"有效子串是否连续"拆开。

栈负责解决匹配问题:

复制代码
当前 ')' 只能匹配最近的、尚未匹配的 '('

isValid 数组负责解决连续长度问题:

复制代码
被标记的位置连续多少个,最长有效括号子串就是多少。

这份写法虽然不是最省空间的栈写法,但思路清晰:先标记所有有效位置,再统计最长连续有效区间。

相关推荐
lvwangshu1 小时前
AC 自动机
算法·字符串
shehuiyuelaiyuehao10 小时前
算法31,前缀和,可被k整除的子数组
数据结构·python·算法
203号居民12 小时前
LeetCode hot 100 — 141. 环形链表2
算法·leetcode·链表
玖玥拾12 小时前
LeetCode 202 快乐数
算法·leetcode
LuminousCPP12 小时前
数据结构-二叉树(六):BFS层序遍历与完全二叉树判断|复用链式队列 + (N_0=N_2+1) 性质证明
c语言·数据结构·笔记·算法·二叉树·宽度优先
ZhouDevin13 小时前
算法论文/数据集3——CLD(TMLR2025)压缩训练集,仅保留对验证集有益的样本
人工智能·深度学习·算法·计算机视觉
Tim_1014 小时前
【LeetCode】29、两数相除
算法·leetcode·职场和发展
船厂电气自动化ai大模型15 小时前
AI大模型与数学/第63课:矩阵定义、矩阵加法、标量乘法(逐级精讲)
数据结构·人工智能·深度学习·线性代数·算法
余额瞒着我当琳15 小时前
算法修炼 chapter 2 双指针进阶、盛最多水的容器、有效三角形的个数、两数之和、三数之和、四数之和
算法