跳到正文
第 32 题困难

最长有效括号

Longest Valid Parentheses

给定只含 '(' 和 ')' 的字符串,求最长的有效(括号正确配对)子串长度。

在力扣中练习

题目描述

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

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

 

示例 1:

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

示例 2:

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

示例 3:

输入:s = ""
输出:0

 

提示:

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

解题思路

主解法

一维 DP · 以右括号结尾

只讨论以 s[i-1] 结尾的有效段,分“相邻配对”和“跨段配对”

枚举所有子串再逐段检查括号匹配是 O(n³)。把状态定义在结尾上:dp[i] 表示以 s[i-1] 结尾的最长有效括号长度。

如果 s[i-1] 是 '(',不可能有以它结尾的有效串,dp[i] = 0。如果它是 ')',分两种:

  • 前一个字符正好是 '(':这两个字符配对,接到 dp[i-2] 后面,dp[i] = dp[i-2] + 2;
  • 前一个字符也是 ')':先跨过 dp[i-1] 这段有效串,再看它前面的字符是不是 '('。若是,dp[i] = dp[i-1] + 2 + dp[i-dp[i-1]-2]。

自拟主例 s = "(()())"。中间的 () 让 dp[3]=2、dp[5]=4;最后的 ) 跨过长度 4 的段,匹配开头的 (,再把更前面的 dp[0] 拼上,得到 6。

图解
最长有效括号 · 以 s[3] 结尾写入答案

dp[i]:以 s[i-1] 结尾的最长有效括号长度;s = "(()())"

0初dp[0]:00dp[1]:00dp[2]:02dp[3]:20dp[4]:0,当前目标·dp[5]:尚未计算·dp[6]:尚未计算0123456
来源子问题候选状态
本轮没有可用候选

写入0

dp[4]=0
未计算初值
相邻的 () 直接由 dp[i-2]+2 得到;遇到 '(' 结尾时 dp 归零。
分步图解

s[3..4] = "()",直接接在 dp[3] 后面:dp[5] = 2 + 2 = 4。

dp[i]
4
最长有效括号 · 以 s[4] 结尾写入答案

dp[i]:以 s[i-1] 结尾的最长有效括号长度;s = "(()())"

0初dp[0]:00dp[1]:00dp[2]:02dp[3]:2,最优候选的来源0dp[4]:04dp[5]:4,当前目标·dp[6]:尚未计算0123456
来源子问题候选状态
dp[3]dp[3] = 24✓

写入4

dp[5]=dp[3] + 2
=2 + 2=4
未计算初值
1 / 3
最后的 ) 先跨过前面长度为 4 的有效段,再找到开头的 '(',把两段拼起来。

边界:dp[0]=dp[1]=0。答案取所有 dp 的最大值,因为最长有效段不一定在末尾。时间 O(n),空间 O(n)。

时间 O(n)空间 O(n)

易错点

  • dp 只开 n 个:dp[n] 对应整个串,需要 n+1 的空间。
  • 跨段时忘记加 dp[i-dp[i-1]-2]:前面的有效段会被丢掉,答案偏小。
  • 下标 i-dp[i-1]-2 可能为负,必须先判断 >= 0 再访问 s。
对照解法

栈 · 未匹配位置

栈里存还没配对的左括号下标,栈底存最后一个断点

时间同为 O(n),栈版本不需要推导转移方程;空间 O(n),但更容易直接还原最长段的起止位置。

从左到右扫描,用栈保存“还没被匹配的位置”:

  • 遇到 '(':把下标入栈;
  • 遇到 ')':先弹出一个位置。如果栈空了,说明这个右括号多余,把它的下标压入栈当新断点;如果栈不空,当前下标减去栈顶,就是以当前位置结尾的有效段长度。

栈底始终是最后一个没匹配上的位置。例如 "(()())",扫描到最后一个 ) 时,栈顶是开头的 ( 的前一个断点 -1,长度就是 5 - (-1) = 6。

时间 O(n),空间 O(n)。

  1. 1栈中先压入 -1,表示起点前的断点。
  2. 2遇到 '(' 压入下标。
  3. 3遇到 ')' 先弹出;栈空则把当前下标压入当断点。
  4. 4栈不空时用 i - 栈顶更新答案。
时间 O(n)空间 O(n)

易错点

  • 栈空时不补断点:后面所有长度都会从错误的起点算起。
  • 忘记先压入 -1:第一个有效段会少算一个位置。

代码与演示

主解法配有图解;对照解法用于比较思路、复杂度或实现方式。

演示输入s = "(()())"

空串和单个字符都不可能有配对,dp[0]、dp[1] 先设为 0。

s
(()())
最长有效括号 · 初值建立初值

dp[i]:以 s[i-1] 结尾的最长有效括号长度;s = "(()())"

0初dp[0]:0,当前目标·dp[1]:尚未计算·dp[2]:尚未计算·dp[3]:尚未计算·dp[4]:尚未计算·dp[5]:尚未计算·dp[6]:尚未计算0123456
来源子问题候选状态
初值,无需候选
dp[0]=0
未计算初值
1 / 7
查看 C++ 代码

延伸阅读

  • “以 i 结尾”的 DP 适合处理带配对、带连续约束的字符串问题;子串不要求连续时会换成另一种状态。
  • 括号匹配还可以用计数器加两次扫描求最长有效段,空间 O(1),但边界更绕。