32. 最长有效括号(C++)
题目描述:
给定一个只包含 '(' 和 ')' 的字符串,找出最长的包含有效括号的子串的长度。
示例 1:
输入: "(()"
输出: 2
解释: 最长有效括号子串为 "()"
示例 2:
输入: ")()())"
输出: 4
解释: 最长有效括号子串为 "()()"
思想1:动态规划
这个问题可以通过动态规划解决。我们定义一个 dp 数组,其中第 i个元素表示以下标为 i 的字符结尾的最长有效子字符串的长度。我们将 dp 数组全部初始化为 0 。现在,很明显有效的子字符串一定以 ‘)’ 结尾。这进一步可以得出结论:以 ‘(’ 结尾的子字符串对应的dp 数组位置上的值必定为 0 。所以说我们只需要更新 ‘)’ 在dp 数组中对应位置的值。
为了求出dp 数组,我们每两个字符检查一次,如果满足如下条件
1.s[i]=‘)’ 且s[i−1]=‘(’ ,也就是字符串形如‘‘……()",我们可以推出:
dp[i]=\dp[i-2]+2
我们可以进行这样的转移,是因为结束部分的 "()" 是一个有效子字符串,并且将之前有效子字符串的长度增加了 2 。
2.s[i−1]=‘)’,也就是字符串形如 ‘‘.......))" ,我们可以推出:
如果 s[i−dp[i−1]−1]=‘(’ ,那么dp[i]=dp[i−1]+dp[i−dp[i−1]−2]+2
这背后的原因是如果倒数第二个‘)’ 是一个有效子字符串的一部分(记为 sub_s),对于最后一个‘)’ ,如果它是一个更长子字符串的一部分,那么它一定有一个对应的‘(’ ,它的位置在倒数第二个 ‘)’ 所在的有效子字符串的前面(也就是 sub_s的前面)。因此,如果子字符串 sub_s
的前面恰好是 (’ ,那么我们就用 2 加上 sub_s的长度dp[i−1])去更新 dp[i]。除此以外,我们也会把有效子字符串 "(,sub_s,)"之前的有效子字符串的长度也加上,也就是加上dp[i−dp[i−1]−2] 。
代码1:
class Solution { public: int longestValidParentheses(string s) { int maxlen = 0; vector<int> dp(s.size()); for(int i=1;i<s.size();i++){ if(s[i]==')'){ if(s[i-1]=='(') dp[i] = 2 + (i>=2?dp[i-2]:0); //注意()的位置,把i>=2?dp[i-2]:0看着一个整体 else if(i-dp[i-1]>0 && s[i-dp[i-1]-1]=='(') dp[i] = 2 + (i-dp[i-1]>=2?dp[i-dp[i-1]-2]:0) + dp[i-1]; maxlen = max(maxlen,dp[i]); } } return maxlen; } };
时间复杂度: O(n) 。遍历整个字符串一次,就可以将 dp 数组求出来。
空间复杂度: O(n) 。需要一个大小为 n 的 dp 数组。
思想2:栈
与找到每个可能的子字符串后再判断它的有效性不同,我们可以用栈在遍历给定字符串的过程中去判断到目前为止扫描的子字符串的有效性,同时确定最长有效字符串的长度。我们首先将 −1 放入栈顶。
对于遇到的每个 ‘(’ ,我们将它的下标放入栈中。
对于遇到的每个 ‘)’ ,我们弹出栈顶的元素并将当前元素的下标与当前栈顶元素作差,得出当前有效括号字符串的长度。通过这种方法,我们继续计算有效子字符串的长度,并最终返回最长有效子字符串的长度。
代码2:
class Solution { public: int longestValidParentheses(string s) { stack<int> myStack; myStack.push(-1); int maxlen = 0; for(int i=0;i<s.size();i++){ if(s[i] == '(') myStack.push(i); //进栈的是下标 else{ myStack.pop(); if(myStack.empty()) myStack.push(i); else{ maxlen = max(maxlen,i-myStack.top()); } } } return maxlen; } };
思想3:left,right计数器