解法一:栈
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} si−1为左括号时
那么
s
i
s_i
si与
s
i
−
1
s_{i-1}
si−1成功配对,因此
f
i
>
=
2
f_i >= 2
fi>=2。
此时,什么情况决定了
f
i
=
2
f_i = 2
fi=2呢?
那便是
s
i
−
1
s_{i - 1}
si−1之前的括号串没法匹配的时候——此时
f
i
−
2
=
0
f_{i - 2} = 0
fi−2=0。
同理,什么情况决定了
f
i
>
2
f_i > 2
fi>2呢?
那便是存在包含
s
i
−
2
s_{i - 2}
si−2的括号串,此时
f
i
−
2
!
=
0
f_{i - 2} != 0
fi−2!=0
综上所述,
f
i
=
f
i
−
2
+
2
f_i = f_{i - 2} + 2
fi=fi−2+2
2. 当 s i − 1 s_{i - 1} si−1为右括号时
可能与
s
i
s_i
si匹配的左括号是否存在呢?如果存在,它在哪个位置呢?
如图所示,借助
f
i
−
1
f_{i-1}
fi−1,我们可以找到在
s
i
s_i
si前的、下标为
i
−
f
i
−
1
−
1
i - f_{i-1}-1
i−fi−1−1的字符为与
s
i
s_i
si可能匹配的位置。
如果这个位置的字符为左括号,那么 s i s_i si将成功匹配,此时, f i > = f i − 1 + 2 f_i >= f_{i - 1} + 2 fi>=fi−1+2,为什么不是 f i = f i − 1 + 2 f_i = f_{i - 1} + 2 fi=fi−1+2呢?因为在 i − f i − 1 − 1 i - f_{i - 1} - 1 i−fi−1−1之前的括号串可能是有效的,它与 i − f i − 1 − 1 i - f_{i - 1} - 1 i−fi−1−1之后的字符可能是因为之前尚未找到匹配的右括号 s i s_i si而暂时隔断。所以,我们还应当考虑 i − f i − 1 − 2 i - f_{i - 1} - 2 i−fi−1−2位置的状态值。
而如果这个位置不是左括号,那么 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(i−2)+2f(i−2−f(i−1))+f(i−1)+20si−1=′(′si−1=′)′且si−f(i−1)−1=′(′si−1=′)′且si−fi−1−1=′)′
题目链接
原创不易,感谢支持!