"""
32. Longest Valid Parentheses
Hard
1343
68
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 "()()"
"""
今天写leetcode的第32题,题目的大概意思是一个字符串s,是由"(",")"两种字符组成的,问其中最长的左右配对的子字符串是多长。诸如()(()),(()()),这样的都是匹配的。
这道题目想了很久才做出来。
从左到右遍历,用一个栈preleftlist来记载"("的数量,用另一个栈tmplist记载连续子字符串的数量,为什么要用两个栈呢,用maxlen记载最长子串长度,用tmp从左到右遍历考虑到这样的情况"()(()"这个的最大子串长度是2,但是如果在最右边再加上一个")"的话就会变成"()(())"这样的话就是6个,也就是最前面的2个可能还有用,所以要用栈来存储数量的信息,具体操作是这样子的从右向左遍历,preleft来记载连续的"("的数量,将preleft进栈到preleftlist栈中,继续遍历,接下来是连续的")",每当有一个")"那么栈preleftlist的顶端的数字就-1,同时tmp+=2;一直如此,直到遇见"(",就将此时的tmp进栈到tmplist中,同时更新maxlen;在此过程中遇见栈preleftlist顶端的数字变成0的话,preleftlist出栈,同时tmplist如果存在栈首就出栈加到tmp上面,这就相当于将原先被"("隔离的两部分加在了一起;如果遇见preleftlist为空的情况的话,说明这个")"找不到可以配对的"("的,此时更新maxlen后就将所有栈清空;代码如下:
class Solution: def longestValidParentheses(self, s): """ :type s: str :rtype: int """ s_len = len(s) preleftlist = [] preleft = 0 maxlen = 0 tmp = 0 tmplist = [] i = 0 while i < s_len: while i < s_len and s[i] == "(": preleft += 1 i += 1 if preleft: preleftlist.append(preleft) preleft = 0 while i < s_len and s[i] == ")": if preleftlist: tmp += 2 preleftlist[-1] -= 1 #这一层的左括号已经用完了 if not preleftlist[-1]: preleftlist.pop() #如果前面成对的()就把数量合并 if tmplist: tmp += tmplist[-1] tmplist.pop() #这个else说明右括号已经找不到可以匹配的左括号了 else: maxlen = max(tmp, maxlen) tmp = 0 tmplist = [] i +=1 #之前的循环中右括号找到的tmp计算进去 if tmp: tmplist.append(tmp) maxlen = max(tmp, maxlen) tmp = 0 return max(tmp, maxlen)
在评论区看到一个很有意思的c++的空间复杂度是O(1)的算法:
int longestValidParentheses(string s) { calcInvalid(s, '('); reverse(s.begin(), s.end()); return calcInvalid(s, ')'); } int calcInvalid(string& s, char plus) { int stack_ = 0, invalid = 0, longest = 0, length = 0; for (int i = 0; i < s.size(); i++) { if (s[i] == plus) { stack_++; } else { if (s[i] == '#' || stack_ == 0) { s[i] = '#'; stack_ = 0; length = 0; continue; } stack_--; } longest = max(longest, ++length); } return longest; }
看了一会才看懂是什么意思,对于字符串s,首先考虑到这几点:
1,每一个右括号要么不配对要么只有唯一的"("与之相对应;
2,从前遍历,和从后遍历本无本质区别
3,每一个"("要么不配对,要么只有唯一的")"与之相对应;
这个算法,我把它分成3段:
1, 基于这样的想法,首先从前向后遍历,有preleft记录"("的数量,遍历到右括号如果preleft>0就将preleft-1,同时说明这个右括号是有配对的,如果preleft==0说明这个右括号是不配对的,将这个右括号改成其他的某个字符,这样就可以找到所有不配对的")";
2, 再从后先前遍历找到所有不匹配的"(",再次改为其他字符,这样剩下的字符都是遍历的了
3,找到字符串中连续的只包含"("或者")"的最长子字符串的长度
步骤1和步骤2就只是从前遍历和从后遍历的区别可以写成一个函数,3完全可以在步骤2中进行统计,这就是为什么c++只有一个函数的原因
可惜的是因为python中str是不可变的,只能先转化为list所以空间复制度就不是O(1)了,下面是我写的python代码:
class Solution: def longestValidParentheses(self, s): """ :type s: str :rtype: int """ s_len = len(s) maxlen = 0 tmp = 0 preleft = 0 lastright = 0 s = list(s) for i in range(s_len): if s[i] == "(": preleft += 1 else: if preleft: preleft -= 1 else: s[i] = "@" preleft = 0 # for i in range(s_len-1,-1,-1): # if s[i] == ")" or s[i] == "@" : # lastright += 1 # else: # if lastright: # lastright -= 1 # else: # s[i] = "@" # lastright = 0 for i in range(s_len-1,-1,-1): if s[i] == ")": lastright += 1 elif(s[i]=="@"): lastright = 0 else: if lastright: lastright -= 1 else: s[i] = "@" lastright = 0 for i in range(s_len): if s[i] != "@": tmp += 1 else: maxlen = max(maxlen, tmp) tmp = 0 return max(maxlen, tmp)
注释部分的代码和下面的是可以互换的,都是步骤2的实现
听说还可以用动态规划做,我等等试着写一下