Leetcode Link: 32. 最长有效括号 - 力扣(LeetCode)
题目
给你一个只包含 '(' 和 ')' 的字符串,找出最长有效(格式正确且连续)括号子串的长度。
解法一: 动态规划
思路: 理解这个动态规划的递推公式,最好尝试举几个例子
dp 五步走
- 一维 dp,
dp[i]表示以下标i字符结尾的最长有效括号的长度 - 初始化:
dp[0] = 0是一定的 - 递推公式,见下
- 遍历方向:正常左到右
- 举例子
关于递推公式
- 可以确定,以
'('结尾的子串对应的dp值必定为0 - 以
')'结尾的子串有如下两种情况:- 若
s[i−1]=='(':即s[i]和s[i−1]组成一对有效括号,有效括号长度新增 2。i位置对最长有效括号长度为 其之前 2 个位置的最长括号长度加上当前位置新增的 2,我们无需知道i-2位置对字符是否可以组成有效括号对。即 - 若
s[i−1]==')':此时,如果前面有和s[i]组成有效括号对的字符,即形如 ( (…) ),就要求s[i - 1]位置 必然是有效的括号对 ,否则s[i]无法和前面对字符组成有效括号对。 这时,需要找到当前s[i]的配对位置,为s[i-dp[i-1]-1](举个例子画个图就知道了),并判断该位置是不是'('。如果不是,则当前位置dp[i]=0,反之为dp[i]=2+dp[i-1]+dp[i-dp[i-1]-2]其中,
dp[i]=2+dp[i-1]表示当前这段((…))的长度,但是如果其前面也是有效的括号话,即(..)((…)),我们需要加上这一段,即再加上dp[i-dp[i-1]-2]
- 若
以上
题解:
class Solution:
def longestValidParentheses(self, s: str) -> int:
if len(s)==0: return 0
dp = [0]*len(s)
for i in range(1, len(s)):
if s[i]==')' and s[i-1] == '(':
dp[i] = dp[i-2]+2 if i-2>=0 else 2
# if X and 前一个是合规括号字符串(暗含s[i-1]==')') and X and 对应的配对括号是否满足要求
if s[i]==')' and dp[i-1] > 0 and i-dp[i-1]-1>=0 and s[i-dp[i-1]-1]=='(':
dp[i] = 2+dp[i-1]+dp[i-dp[i-1]-2] if i-dp[i-1]-2>=0 else 2+dp[i-1]
return max(dp) 解法二 (错误提醒)
思路:动态规划
二维不行
二维不行,开始我想的二维是考虑三种情况,即左右两个,左边两个,右边两个的括号,
dp[L][R]的含义是L..R上是不是有效(True or False),我错误的认为如果新探查的两个位置是()就一定是有效的,然而如果是((也有可能,譬如”(())(…)”此时就不能有效的判定了
基于错误的 2 维 dp 的递归公式,以此为戒!
if s[L]=="(" and s[R] == ")" and dp[L+1][R-1] is True:
dp[L][R] = True
elif L+2<=R and s[L] == "(" and s[L+1] == ")" and dp[L+2][R] is True:
dp[L][R] = True
elif R-2>=L and s[R] == ")" and s[R-1] == "(" and dp[L][R-2] is True:
dp[L][R] = True题解:
解法三 (自己的灵光乍现)
思路:一个突发奇想,我也可能解释不清楚的。 先做删除无效的括号,体会一下从解法一怎么到解法二
易知,可以通过 score 指示当前的括号字符串是不是合规的,有
score < 0一定违规score = 0一定合规score > 0有可能有一部分合规
为什么 score > 0 不一定合规?
假设我们按照删除无效的括号中,遇到 (,score+1,遇到 ) 分情况处理。显然,对于 ((),我们的 score 一直都大于 0,此时是有合规的部分的,但是我们 score 一直不等于 0,找不到。
那咋办?
逆序!再来!想想为啥我们 score 一直不等于 0,因为代表-1 的括号数量不够。正常情况下,先左再右;逆序之后,匹配的顺序就是先右再左了,此时遇到 ) 时 score+1,反之分情况。
我们可以用一个 cnt 变量指示当前合规的有多长,一旦遇到 score==0还需要score-1 的情况,就知道只有 cnt 这么长合规,下面就开启新的合规统计。
然而,有两个细节:
- for 循环遍历完之后,如果恰好
score==0,此时循环体里的判定是走不到的,所以 for 外面我们要判断一下 )()()(这种输入,无论正走反走,都不会遇到score==0还需要减一的情况,但是会出现合规的score==0,所以在每个for的开始我们都要判定一下。
题解:
class Solution:
def longestValidParentheses(self, s: str) -> int:
cnt = 0 # 当前合规长度计数
res = -float('inf') # 初始化
score = 0
for c in s:
# 对应细节2
res = max(res, cnt) if score == 0 else res
if c == "(":
score += 1
cnt += 1
else:
if score > 0:
cnt += 1
score -= 1
else:
res = max(res, cnt)
score = 0
cnt = 0
# 对应细节1
res = max(res, cnt) if score == 0 else res
s = s[::-1]
score = 0
cnt = 0
for c in s:
res = max(res, cnt) if score == 0 else res
if c == ")":
score += 1
cnt += 1
else:
if score > 0:
score -= 1
cnt += 1
else:
res = max(res, cnt)
score = 0
cnt = 0
res = max(res, cnt) if score == 0 else res
return res解法四:栈 (参考的,很巧)
思路 对于这种括号匹配问题,一般都是使用栈
我们先找到所有可以匹配的索引号,然后找出最长连续数列!
例如:s = ')(()())',我们用栈可以找到,
位置 2 和位置 3 匹配,
位置 4 和位置 5 匹配,
位置 1 和位置 6 匹配,
这个数组为:2,3,4,5,1,6 这是通过栈找到的,我们按递增排序!1,2,3,4,5,6
找出该数组的最长连续数列的长度就是最长有效括号长度!
所以时间复杂度来自排序:。
题解
class Solution:
def longestValidParentheses(self, s: str) -> int:
stack = [] # 栈
ind = [] # 记录有效的成对索引
for i, c in enumerate(s):
if c == '(':
stack.append(i)
else:
if len(stack) != 0:
ind.append(stack.pop())
ind.append(i)
else:
continue
ind.sort()
cnt = 1
res = -float('inf')
# 寻找最长连续子串长度
for i in range(1, len(ind)):
if ind[i] - ind[i-1] >= 2:
res = max(res, cnt)
cnt = 1
else:
cnt += 1
res = max(res, cnt)
# 当 res == 1 说明为空,因为不可能只有一个括号。提前令cnt=1是为了减少判断
return 0 if res == 1 else res