#32 困难

最长有效括号

在 LeetCode 上查看 ↗

🎧 语音讲解

开车或通勤时可听,跟着思路走一遍

速度

📋 题目描述

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

左右括号匹配,即每个左括号都有对应的右括号将其闭合的字符串是格式正确的,比如 "(()())"

示例 1

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

示例 2

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

示例 3

输入:s = ""
输出:0

💭 模拟答题者思考

1. 我先想暴力:枚举所有子串,对每个子串用栈或计数判断括号是否有效,取最长——能过但 O(n³) 或 O(n²),3×10⁴ 的数据会超时。

2. 重复在哪里?每遇到一个 ')',它只能与「离它最近、尚未匹配」的 '(' 配对——又是后进先出;但本题要的是最长连续有效子串,不是判断整串是否有效。

3. 关键转化:栈里不存字符,存下标。压入哨兵 -1 表示「有效段起点的前一位」;每次 ')' 弹出匹配的 '(' 后,当前有效段长度 = 当前下标 - 栈顶下标

4. 若弹出后栈空,说明这个 ')' 无法配对(如开头就是 ')'),把它压回栈作为新的「分割点」,后面的有效段从这里重新计算。

5. 例 ")()())":遇到开头 ')' 后栈只剩 [2] 作基准,随后 () 得长度 2,再 () 得长度 4;全程 O(n) 一遍扫描。

🧠 变量语义(先读这三句再编码)

变量类型语义(三句法)
stklist / stack定义:存放「尚未被匹配的左括号下标」以及作为基准的哨兵下标
维护:栈顶对应当前有效子串的「左边界前一位」;初始压入 -1 作为全局基准
更新:遇 '(' 压入下标 i;遇 ')'pop,栈空则压入 i 重置基准,否则用 i - stk[-1] 更新答案
ansint定义:截至目前发现的最长有效括号子串长度
维护:单调不减,记录全局最优
更新:每次成功匹配右括号后,计算 i - stk[-1] 并与 ansmax
iint定义:当前扫描到的字符下标
维护:从左到右依次处理每个括号
更新:每轮循环 i += 1,根据 s[i] 是左/右括号分支更新栈与答案

⌨️ 落码步骤

1. 初始化 stk = [-1](哨兵)、ans = 0

2. 从左到右遍历下标 i 与字符 s[i]

3. 若 s[i] == '(',将 i 压入栈,等待后续右括号匹配

4. 若 s[i] == ')':先 stk.pop() 弹出待匹配的左括号(或哨兵)

5. 弹出后若栈空,说明当前 ')' 无法配对,将 i 压栈作为新基准;否则 ans = max(ans, i - stk[-1])

6. 遍历结束返回 ans

💻 代码实现

class Solution:
    def longestValidParentheses(self, s: str) -> int:
        stk = [-1]          # 哨兵:有效段左边界的前一位
        ans = 0
        for i, ch in enumerate(s):
            if ch == '(':
                stk.append(i)
            else:
                stk.pop()   # 匹配掉一个 '(' 或哨兵
                if not stk:
                    stk.append(i)   # 多余的 ')',作为新分割点
                else:
                    ans = max(ans, i - stk[-1])
        return ans
class Solution {
public:
    int longestValidParentheses(string s) {
        vector<int> stk = {-1};  // 哨兵:有效段左边界的前一位
        int ans = 0;
        for (int i = 0; i < (int)s.size(); i++) {
            if (s[i] == '(') {
                stk.push_back(i);
            } else {
                stk.pop_back();  // 匹配掉一个 '(' 或哨兵
                if (stk.empty()) {
                    stk.push_back(i);  // 多余的 ')',作为新分割点
                } else {
                    ans = max(ans, i - stk.back());
                }
            }
        }
        return ans;
    }
};
// 时间 O(n),空间 O(n)

📈 复杂度分析

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

⚠️ 常见坑

栈里存字符而不是下标:无法计算子串长度,也无法在多余 ')' 处设置分割点。

忘记初始哨兵 -1:第一个完整段 "()"i=1 时栈顶为空,长度会变成 1-0=1 而非 2。

弹出后栈空时忘记压入当前 i:后续有效段会把前面无法配对的 ')' 也算进去,导致长度偏大。

🔍 必测边界 Case

Case 1:空串
s = "" → 0
Case 2:只有左括号
s = "(()" → 2(末尾多余 '(' 留在栈中,不影响已算出的 "()"
Case 3:以右括号开头
s = ")()())" → 4(开头 ')' 触发重置基准,最长段为 "()()"