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 数组负责解决连续长度问题:

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

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

相关推荐
Navigator_Z3 小时前
LeetCode //C - 1289. Minimum Falling Path Sum II
c语言·算法·leetcode
CCblandy4 小时前
令牌桶在 0.9 倍负载超发 19%、固定窗口边界放行 20 个:四种限流算法同构实测
后端·python·算法·性能优化·架构
YangYang9YangYan5 小时前
2027供应链计划校招:数学与应用数学专业如何把预测模型讲成业务决策?
人工智能·sql·算法
jsjzsl25 小时前
递归自改进系统(RSI)的本质是自发对称破缺:智能爆炸有界—— AI研发切记盲目投入
人工智能·python·算法
程序员清风5 小时前
最短路径算法:BFS、Dijkstra 与 Floyd
java·算法·宽度优先
梦帮科技5 小时前
Kali Linux Scapy 畸形报文构造学——深度解剖非法 TCP Flags 与反常序列号
linux·爬虫·神经网络·算法·数学建模·系统架构·模拟退火算法
171320330计算机毕设编程7 小时前
计算机毕业设计 基于YOLO改进算法的车辆特征识别系统设计与实现--lw
java·大数据·python·sql·算法
姜鱼问生7 小时前
Nginx gzip 压缩:一个配置让页面加载快 3 倍
后端·算法·架构
不会就选b7 小时前
算法日常・每日刷题--<动态规划>23
算法·动态规划