原题网址:https://leetcode.com/problems/longest-valid-parentheses/
难度:Hard
Given a string containing just the characters ‘(’ and ‘)’, find the length of the longest valid (well-formed) parentheses substring.
Example 1:
Input: “(()”
Output: 2
Explanation: The longest valid parentheses substring is “()”
Example 2:
Input: “)()())”
Output: 4
Explanation: The longest valid parentheses substring is “()()”
题目给定一个由若干圆括号组成的序列,要求从中找出最长的合法子序列。相比找出最长的合法子序列,要判断一串圆括号序列是否合法并不是很难,一种较为简单的做法是通过栈来实现。从左至右逐个读取括号,若为左括号则压入栈,若为右括号则判断栈是否为空,若为空则说明该右括号无法匹配到左括号,若不为空则弹出栈顶(入栈的都是左括号)继续读入下一个括号,直到所有括号都读完后栈为空则说明序列合法。复杂度为O(n)。
所以有一种做法是遍历所有的(i,j)(0 <= i < j < len),判断子串str[i:j+1]是否为合法序列,并记录当前最长的合法序列长度。算法总的复杂度达到 O ( n 3 ) O(n^3) O(n3)。
我们还可以试着将该问题分解成子问题,由子问题逐步求解出最终问题。一个最长合法序列如何分步得到呢?一个自然的想法是较短的两个合法序列拼接成更长的序列。对于括号序列,有两种方式从短的合法序列增长成更长的合法序列:一种是嵌套,通过给合法序列外加一层括号得到新的合法序列,另一种是拼接,两个合法的括号序列可以拼接一个更长的合法序列。
既然如此,那我们的最小子问题就可以设计成寻找“()”,这是最短的合法序列。然后再判断该序列能否扩展成"()()“或”(())",以此类推,逐渐找出更长的合法序列。
在算法实现上,我们还需要进一步确定如何记录我们找到的合法匹配的括号,以下给出一种实现(合法匹配的括号在字符串中标记为0,最终在标记好的字符串中寻找最长的连续0序列即可知道最长合法序列的长度):
示例:给定序列'()())((()))'
第一趟遍历字符串:将所有'()'都标记为'\0\0'
得到标记后的字符串'\0\0\0\0)((\0\0))'
在下一趟遍历中'\0'会被忽略,相当于在遍历字符串')(())'(将所有的'\0'去掉)
第二趟遍历字符串(遇到'\0'则跳过):将所有'()'都标记为'\0\0'
得到标记后的字符串'\0\0\0\0)(\0\0\0\0)'
在下一趟遍历中'\0'会被忽略,相当于在遍历字符串')()'(将所有的'\0'去掉)
第三趟遍历字符串(遇到'\0'则跳过):将所有'()'都标记为'\0\0'
得到标记后的字符串'\0\0\0\0)\0\0\0\0\0\0'
在下一趟遍历中'\0'会被忽略,相当于在遍历字符串')'(将所有的'\0'去掉)
第四趟遍历字符串(遇到'\0'则跳过):将所有'()'都标记为'\0\0'
这一趟遍历并未找到任何'()',说明所有能匹配的括号已经匹配完毕,终止循环
最终得到的标记字符串:'\0\0\0\0)\0\0\0\0\0\0'
最长的连续'\0'序列长度为6,对应的括号序列为'((()))',即最长合法序列
由上面的循环过程也可以看出我们是逐层的匹配括号的(从最内层括号开始匹配)
对应于上述过程的代码实现如下:
// leetcode评测成绩: 4ms 排名前56.25%
func longestValidParentheses(s string) int {
str := []byte(s)
const left = byte('(')
const right = byte(')')
var state int
// 逐趟匹配括号
for ;; {
state = 0
matched := false
for i := 0;i < len(str);i++ {
switch str[i] {
// 忽略被标记的字符(已匹配的括号)
case 0:
continue
// 读到左括号则更新state值,记录该左括号的位置
case left:
state = i + 1
// 读取到右括号则判断在它前面是否有左括号与之配对(如果有则左括号的下标为state-1)
case right:
if state != 0 {
str[state - 1] = 0
str[i] = 0
state = 0
matched = true
}
}
}
if !matched {
break
}
}
// 计算标记字符串中最长的连续\0序列
maxLength := 0
for i := 0;i < len(str);i++ {
if str[i] != 0 {
continue
}
for j := i + 1;j < len(str);j++ {
if str[j] == 0 && (j == len(str) - 1 || str[j + 1] != 0) {
if j - i + 1 > maxLength {
maxLength = j - i + 1
}
i = j + 1
break
}
continue
}
}
return maxLength
}