32. Longest Valid Parentheses(最长括号匹配,hard)
Given a string containing just the characters '('
and ')'
, find the length of the longest valid (well-formed) parentheses substring.
For "(()"
, the longest valid parentheses substring is "()"
, which has length = 2.
Another example is ")()())"
, where the longest valid parentheses substring is "()()"
, which has length = 4.
class Solution: def longestValidParentheses(self, s: str) -> int: res1 = l = r = 0 for ch in s: if ch =='(': l+=1 else: r+=1 if r==l: res1 = max(res1,l*2) elif r>l: l = r = 0 res2 = l = r = 0 for ch in s[::-1]: if ch ==')': r+=1 else: l+=1 if l==r: res2 = max(res2,l*2) elif l>r: l = r = 0 return max(res1,res2)
最长有效括号
括号类问题的综合考查 四种方法分析记录
最长有效括号,其实有效括号问题已经是很经典的问题了,我们可以利用栈来判断有效括号的数量,也可以利用左右括号的数量是否匹配来判断这个括号序列是否有效,这些都是经典的方法,而最让人感到欣喜的是,这些用于最经典的问题的方法同样适用于这样一道困难题——“最长有效括号”,正所谓,“万变不离其宗”啊。
首先我们先看我们判断有效括号数量长度的一般栈写法:
stack<int> stk; for (int i = 0; i < s.length(); i++) { if (s(i) == '(') { // 遇到左括号,记录索引 stk.push(i); } else { // 遇到右括号 if (!stk.empty()) { // 配对的左括号对应索引,[leftIndex, i] 是一个合法括号子串 int leftIndex = stk.top(); stk.pop(); // 这个合法括号子串的长度 int len = 1 + i - leftIndex; } else { // 没有配对的左括号 } } }
那么这种清晰的栈写法是否适用于这道题目呢,显然不适用,为什么?因为最长,多个有效的括号子串会组合成一个更长的合法括号子串,那么由此我们想到需要更新维护前i个字符串的最长括号数量,由此想到动态规划,而这就是本题的第一个解法,也是常规动态规划的写法,dp[i] 的定义:记录以 s[i-1] 结尾的最长合法括号子串长度,我们依旧用栈存储左括号的索引,详细细节代码可见。
class Solution { public: int longestValidParentheses(string s) { int n=s.size(); int res=0; stack<int> stk; vector<int> dp(n+1); for(int i=1;i<=n;i++){ if(s[i-1]=='(')stk.push(i); else{ if(!stk.empty()){ int index=stk.top(); stk.pop(); dp[i]=dp[index-1]+i-index+1; } } res=max(res,dp[i]); } return res; } };
1 class Solution { 2 public: 3 int longestValidParentheses(string s) { 4 vector<int> dp(s.size(),0); 5 int res = 0; 6 for(int i = 1; i < dp.size();i++) { 7 if (s[i]==')') { 8 if (s[i-1]=='(') { 9 if (i-2>=0) { 10 dp[i] = dp[i - 2] + 2; 11 } else { 12 dp[i] = 0 + 2; 13 } 14 15 } else {//s[i-1]=='(' 16 if(i-dp[i-1]-1 >=0 && s[i-dp[i-1]-1]=='(') { 17 if (i-dp[i-1]-2>=0) { 18 dp[i] = dp[i-dp[i-1]-2] + dp[i-1] + 2; 19 } else { 20 dp[i] = dp[i-1] + 2; 21 } 22 } 23 } 24 } 25 res = max(res,dp[i]); 26 } 27 return res; 28 } 29 };
其实一开始栈之所以不能解决就是因为无法处理多个有效子串区间拼接形成的最大值,我们可以考虑让栈底维护一个当前遍历过的元素中最后一个没有被匹配的右括号的下标,其实就是以它为无效的边界,下一个最长的要么是它前面那一段,要么是后面那一段中,那么遇到左括号就入栈,遇到右括号我们就先弹出一个,如果栈非空说明左右可以匹配,此时和当前右括号匹配的最大长度就是当前索引i减去弹出一个元素的栈顶下标(因为已经弹出一个,所以不需要+1),同时,为了使整体上符合这样的规则,我们先将-1入栈,因为考虑到正常长度+1的性质,因此减去-1就等同于+1了,我们可以假设一个整个串都是有效的例子来看,比如,“( )( )(( ))”这样的例子,试看这个栈执行的过程,因为-1在栈底,所以最大值一直能够维护更新到最后一个索引减去-1。确实是很巧妙的代码!但思路来源也不是空穴来风,是有迹可循的,正如我一开始所说的,这是很令人欣喜的,第三个解法就有了。
1 class Solution: 2 def longestValidParentheses(self, s): 3 ans = 0 4 stack = [] 5 last =-1 6 for i,item in enumerate(s): 7 if item == '(': 8 stack.append(i) 9 else: 10 if(stack == []):#已经匹配成功了 11 last = i 12 else: 13 stack.pop() 14 if stack==[]: 15 ans = max(ans,i-last) 16 else: 17 ans = max(ans,i-stack[-1]) 18 return ans
其实第四个解法还是基于一开始提到的基本方法的,我们除了可以利用栈来判断有效之外,我们还可以利用左右括号的数量是否匹配来利用计数器快速判断嘛,其实解法四就是来源于此,用左右计数器left和right分别维护左右括号的数量,如果相等说明匹配,那么括号的长度不就是已经匹配的左或者右括号计数器的两倍嘛,同样,我们基于贪心策略,考虑从前往后遍历出现左括号的数量一直大于右括号的数量,我们从后前颠倒左右括号的角色再遍历一次即可,如此,最低空间复杂度效率最高的解法四横空出世了!
class Solution { public: int longestValidParentheses(string s) { int res = 0; int l_cnt =0; int r_cnt = 0; for(int i = 0; i < s.size();i++) { if (s[i] == '(') l_cnt++; else r_cnt++; if (l_cnt==r_cnt) res = max(res,r_cnt*2); if (l_cnt<r_cnt) l_cnt = r_cnt = 0; } r_cnt = l_cnt = 0; for(int i = s.size()-1; i >= 0;i--) { if (s[i] == '(') l_cnt++; else r_cnt++; if (l_cnt==r_cnt) res = max(res,r_cnt*2); if (r_cnt<l_cnt) l_cnt = r_cnt = 0; } return res; } };