[力扣]32. 最长有效括号(三解法)

29 篇文章 0 订阅
23 篇文章 0 订阅

解法一:栈

class Solution:
    def longestValidParentheses(self, s: str) -> int:
        q = []
        st = -1
        res = 0
        for i in range(len(s)):
            if s[i] == '(': q.append(i)
            elif q:
                q.pop()
                if q:res = max(res,i - q[-1])
                else:res = max(res,i - st)
            else:
                st = i
        return res

使用栈结构来实现。
这里利用了括号匹配的一个潜在的性质,即:

合法的括号串中,前导的左括号数量必定大于等于右括号数量。

根据这个性质,所以如果从前向后遍历,出现左括号数量小于右括号数量时,那么从当前位置之前开始的所有匹配的串必定在此之前截断。如下所示:

在这里插入图片描述
从下标0开始,到4的位置,出现右括号数量大于左括号。此时,如果存在在下标4之前开始的有效括号,那么它必定在下标4或下标4之前被截断。下标9也是同样的道理。

进一步我们不难看出,最长的有效括号必定存在于0~3,5~9或10~11之中的某一段。

在这里插入图片描述
因此,我们可以将图中的括号串分成三段,用一个指针指向每段开始的位置(第一段位置为假定的-1位置)。

对于每一段,我们可以使用一个栈结构,在遍历过程中,将左括号的位置逐个入栈。在遇到右括号时,将栈顶下标取出,此时该栈顶下标即为与当前右括号匹配的左括号的下标。此时对应第十行代码:

if q:res = max(res,i - q[-1])

当然,如果弹出后栈为空,那么表示从该段start位置到当前右括号位置的字符串为有效括号序列。

res = max(res,i - st)

同时,因为每段的遍历结束后,栈中元素自然为空,所以不需要为每一段单独定义一个栈。

解法二:双向遍历

class Solution:
    def getRes(self,s,c):
        l,r = 0,0
        res = 0
        for t in s:
            if t == c: l += 1
            else: r += 1
            if l < r: l,r = 0,0
            elif l == r:res = max(res,l + r)
        return res
    def longestValidParentheses(self, s: str) -> int:
        return max(self.getRes(s,'('),self.getRes(s[::-1],')'))

该代码相对于解法一更加简练,而其理解难度也相对较低。
这里同样运用了第一个解法中涉及到的性质:

合法的括号串中,前导的左括号数量必定大于等于右括号数量。

在函数getRes中,会对字符串做单向遍历,找出其中最长的合法的括号序列的长度。
但是,在该单向遍历中,有一部分括号却因为特殊的情况没能发现,例如:
在这里插入图片描述
如图所示,在下标1的位置时, l = = r l == r l==r,成功更新 r e s = 2 res = 2 res=2
在最后一个下标位置时, l > r l > r l>r,没有更新 r e s = 4 res = 4 res=4。即少比较了[3,6]的括号串。
为什么呢?
原因出在了 [ 2 , 4 ] [2,4] [2,4]上,这里偏偏多了一个括号,导致在最后位置时, l = = r l == r l==r不成立。
因此我们的问题是如何“抓”到这种被忽略的括号串。
一个非常简单的处理方法便是,将括号串翻转过来,再次进行匹配。
这里需要注意,这里“翻转”的意思,是对括号串做镜像翻转,而不是一个reverse

解法三:动态规划

class Solution:
    def longestValidParentheses(self, s: str) -> int:
        s = ' ' + s
        f = [0] * 100000
        for i in range(2,len(s)):
            if s[i] == '(':continue
            if s[i - 1] == '(': f[i] = f[i - 2] + 2
            elif s[i - 1 - f[i - 1]] == '(':f[i] = f[i - 2 - f[i - 1]] + f[i - 1] + 2
        return max(f)

第三种方法,动态规划。
这可能是一部分人最喜欢的一种方法,它的代码很短,状态转移也很容易推,唯一的难点应该是可能想不到合适的状态表示。

状态表示:
f i f_i fi表示以下标 i i i结尾的有效括号的长度。

因为有效的括号串必定以右括号结束,所以不难得出,所有左括号位置的状态值都为0,我们只需要对右括号所在的状态值进行计算即可。

s i s_i si为右括号时, f i f_i fi的值怎么确定呢?

1. 当 s i − 1 s_{i - 1} si1为左括号时

那么 s i s_i si s i − 1 s_{i-1} si1成功配对,因此 f i > = 2 f_i >= 2 fi>=2
此时,什么情况决定了 f i = 2 f_i = 2 fi=2呢?
那便是 s i − 1 s_{i - 1} si1之前的括号串没法匹配的时候——此时 f i − 2 = 0 f_{i - 2} = 0 fi2=0
同理,什么情况决定了 f i > 2 f_i > 2 fi>2呢?
那便是存在包含 s i − 2 s_{i - 2} si2的括号串,此时 f i − 2 ! = 0 f_{i - 2} != 0 fi2!=0

综上所述,
f i = f i − 2 + 2 f_i = f_{i - 2} + 2 fi=fi2+2

2. 当 s i − 1 s_{i - 1} si1为右括号时

可能与 s i s_i si匹配的左括号是否存在呢?如果存在,它在哪个位置呢?
在这里插入图片描述
如图所示,借助 f i − 1 f_{i-1} fi1,我们可以找到在 s i s_i si前的、下标为 i − f i − 1 − 1 i - f_{i-1}-1 ifi11的字符为与 s i s_i si可能匹配的位置。

如果这个位置的字符为左括号,那么 s i s_i si将成功匹配,此时, f i > = f i − 1 + 2 f_i >= f_{i - 1} + 2 fi>=fi1+2,为什么不是 f i = f i − 1 + 2 f_i = f_{i - 1} + 2 fi=fi1+2呢?因为在 i − f i − 1 − 1 i - f_{i - 1} - 1 ifi11之前的括号串可能是有效的,它与 i − f i − 1 − 1 i - f_{i - 1} - 1 ifi11之后的字符可能是因为之前尚未找到匹配的右括号 s i s_i si而暂时隔断。所以,我们还应当考虑 i − f i − 1 − 2 i - f_{i - 1} - 2 ifi12位置的状态值。

而如果这个位置不是左括号,那么 s i s_i si将不能成功与之匹配, s i s_i si落单了,此时 f i = 0 f_i=0 fi=0

综上所述,
f ( i ) = { f ( i − 2 ) + 2 s i − 1 = ′ ( ′ f ( i − 2 − f ( i − 1 ) ) + f ( i − 1 ) + 2 s i − 1 = ′ ) ′ 且 s i − f ( i − 1 ) − 1 = ′ ( ′ 0 s i − 1 = ′ ) ′ 且 s i − f i − 1 − 1 = ′ ) ′ f(i)= \begin{cases} f(i-2)+2& s_{i-1} = '('\\ f(i - 2 - f(i - 1)) + f(i - 1) + 2& s_{i-1} = ')' 且s_{i - f(i - 1) - 1} = '('\\ 0 & s_{i - 1} = ')' 且 s_{i - f{i - 1} - 1} = ')' \end{cases} f(i)=f(i2)+2f(i2f(i1))+f(i1)+20si1=(si1=)sif(i1)1=(si1=)sifi11=)


题目链接
原创不易,感谢支持!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wingaso

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值