• 【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
    }
    
  • 相关阅读:
    Monkey学习笔记(一)
    10.18 nslookup:域名查询工具
    10.22 tcpdump:监听网络流量
    10.22 tcpdump:监听网络流量
    Linux运维常见笔试题(选择题)
    Linux运维40道精华题
    LeetCode Isomorphic Strings 对称字符串
    LeetCode 3Sum Closest 最近似的3sum(2sum方法)
    博弈的图论模型——必败态与核
    威佐夫博弈(证明)
  • 原文地址:https://www.cnblogs.com/yinbiao/p/16130322.html
Copyright © 2020-2023  润新知