【LeetCode】最长有效括号 (dp/栈统计/栈标记再统计/双向双指针)
最长有效括号
题目链接:https://leetcode-cn.com/problems/longest-valid-parentheses/
给你一个只包含 '(' 和 ')' 的字符串,找出最长有效(格式正确且连续)括号子串的长度。
示例 1:
输入:s = "(()"
输出:2
解释:最长有效括号子串是 "()"
示例 2:
输入:s = ")()())"
输出:4
解释:最长有效括号子串是 "()()"
示例 3:
输入:s = ""
输出:0
提示:
0 <= s.length <= 3 * 104
s[i] 为 '(' 或 ')'
问题分析:
方案1:动态规划
最值问题,应该是一个最值类的dp问题,考虑拆分子问题,弄清楚子问题和我们要解决的大问题之间的联系
-
前一个子问题的解可以提供给后面的子问题多大的有效长度?(子问题)
-
后一个子问题的解,要想纳入前面提供的有效长度,则前面子串的末尾必须是有效长度的一部分(连续性)
我们设dp[i]代表子问题i的解
即dp[i]:以s[i]结尾的有效子串的最长长度,注意:有效子串必须以s[i]结尾,这样才能将每个子问题关联起来
我们可以分类讨论s[i]是不同字符的情况
-
s[i]是左括号:dp[i]=0,因为左括号结尾必然构不成有效字符串
-
s[i]是右括号:这种情况下,还需要考虑相连的s[i-1]是什么字符
-
s[i-1]是左括号,那么s[i]和s[i-1]刚好匹配,那么dp[i]=dp[i-2]+2
-
s[i-1]是右括号,这种情况下,我们要分析与s[i]匹配的字符s[k]到底是什么字符,k=i-dp[i-1]-1
-
k是右括号,都是右括号,不匹配,跳过
-
k是左括号,匹配,dp[i]=x+2,x其实是两个绿色部分的最长有效括号值相加,即x=dp[i-1]+dp[i-dp[i-1]-2],即此时dp[i]=dp[i-1]+dp[i-dp[i-1]-2]+2
-
-
根据以上分析出的子问题间的关系(即前后元素dp值的关系),即可求解出所有的子问题的值,最后找出子问题中解中的最大值,即是我们的最长子串的有效长度
时间复杂度:O(N)
空间复杂度:O(N)
func max(a, b int) int {
if a > b {
return a
}
return b
}
func longestValidParentheses(s string) int {
dp := [30005]int{} // dp[i]: 以s[i]结尾的最长有效子串长度
maxDp := 0
for i := 1; i < len(s); i++ {
if s[i] == '(' {
dp[i] = 0
} else {
if s[i-1] == '(' {
// 避免下标越界
if i-2 >= 0 {
dp[i] = dp[i-2] + 2
} else {
dp[i] = 2
}
} else if i-dp[i-1]-1 >= 0 && s[i-1] == ')' {
k := i - dp[i-1] - 1 // 根据k位置字符的不同再次分类讨论
if s[k] == '(' {
var x int
// 避免下标越界
if i-dp[i-1]-2 >= 0 {
x = dp[i-1] + dp[i-dp[i-1]-2]
} else {
x = dp[i-1]
}
dp[i] = x + 2
}
}
}
// 寻找最大的dp值,即是最长有效子串长度
maxDp = max(maxDp, dp[i])
}
return maxDp
}
方法2:利用栈直接统计最长有效长度
始终保持栈底元素为当前已经遍历过的元素中【最后一个没有被匹配的右括号的下标】,栈里其他元素维护左括号的下标
- 遇到每个'('左括号,将其下标放入栈中
- 遇到每个')'右括号,我们先弹出栈顶元素(代表匹配成功),然后再判断
- 如果栈不为空,说明有对应的左括号与之匹配,当前右括号下标减去栈顶元素即为【以该右括号结尾的最长有效括号长度】
- 如果栈为空,我们将其放入栈中,以更新前面提到的【最后一个没有被匹配的右括号的下标】
- 注意:一开始栈为空,如果第一个字符为左括号,那么就不满足【最后一个没有被匹配的右括号的下标】,所以这种情况下,我们一开始向栈中放入一个值为-1的元素
时间复杂度:O(N)
空间复杂度:O(N)
func max(a, b int) int {
if a > b {
return a
}
return b
}
func longestValidParentheses(s string) int {
if len(s)==0{
return 0
}
var st []int
var result int
st=append(st,-1)
for i:=0;i<len(s);i++{
if s[i]=='('{ // 当前值是左括号,入栈
st=append(st,i)
}else {
st=st[:len(st)-1] // 当前值是右括号,弹出栈顶元素
if len(st)==0{ // 栈空了,将当前右括号入栈
st=append(st,i)
}else {
result=max(result,i-st[len(st)-1]) // 栈没空,计算有效连续长度,并记录最大值
}
}
}
return result
}
方法3:利用栈直接标记出合法括号,然后才统计最大长度
利用栈先入先出的性质,将合法括号置为1,然后统计连续1的最大长度
具体思路:
-
栈记录左括号,遇到右括号就和栈顶的左括号匹配
-
数组flag记录那些括号是已经匹配的,flag[i]=1
-
最后遍历数组,连续最长的1的长度即是最长有效括号
时间复杂度:O(N)
空间复杂度:O(N)
func max(a, b int) int {
if a > b {
return a
}
return b
}
func longestValidParentheses(s string) int {
if len(s)==0{
return 0
}
var stack []int
var flag [30005]int
// 利用栈将合法括号标记为1
for i:=0;i<len(s);i++{
if s[i]=='('{
stack=append(stack,i)
}else {
if len(stack)!=0{
flag[i]=1
flag[stack[len(stack)-1]]=1
stack=stack[:len(stack)-1]
}
}
}
// 统计连续的最长合法括号长度
result:=0
count:=0
for i:=0;i<len(s);i++{
if flag[i]==1{
count++
result=max(result,count)
}else {
count=0
}
}
return result
}
方法四:双向双指针遍历统计
核心:遍历串时,如果串的右括号数量大于左括号数量,那么该串肯定不是合法括号串,因为就算后面再出现了左括号,也无法与前面的右括号配对了
左括号指针:统计左括号数量,left
右括号指针:统计右括号数量,right
采用两个指针,遍历整个大串
- 当right>left,那么left,right重置为0
- 当right==left,记录最大值
- 当right<left,继续遍历统计
但这样会漏掉一种情况,即左括号数量始终都是大于右括号数量的情况,解决方法就是从右往左再遍历一遍,只不过判断条件需要反过来
-
当right<left,那么left,right重置为0
-
当right==left,记录最大值
-
当right>left,继续遍历统计
时间复杂度:O(N)
空间复杂度:O(1)
func max(a, b int) int {
if a > b {
return a
}
return b
}
func longestValidParentheses(s string) int {
if len(s)==0{
return 0
}
result:=0
// 从左往右 统计
left,right:=0,0
for i:=0;i<len(s);i++{
if s[i]=='('{
left++
}else {
right++
}
if left==right{
result=max(result,left+right)
}
if right>left{
left,right=0,0
}
}
// 从右往左 统计
left,right=0,0
for j:=len(s)-1;j>=0;j--{
if s[j]=='('{
left++
}else {
right++
}
if left==right{
result=max(result,left+right)
}
if right<left{
left,right=0,0
}
}
return result
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南