【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
}
posted @   西*风  阅读(66)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
点击右上角即可分享
微信分享提示