求有效括号匹配子串的最大长度。
括号匹配类的问题往往都能用栈解决,此题也不例外。
从前往后遍历字符串,设下标为i
- 如果遇到左括号或者栈空,则将当前下标i压入栈;
- 如果遇到右括号且栈顶为左括号,则遇到了匹配的括号对,将栈顶弹出即可;
然后就可以计算以字符s[i]
作为结尾的最大长度:
- 若栈不空,则长度为
i - stk.top()
; - 否则,即栈空,说明前面的都能匹配,则长度为
i+1
。
时空复杂度均为O(n)
由于此问题存在很多子问题,且子问题之前又有很多重复,所可以考虑用动归。
先来看状态定义:
dp[i]: 以字符s[i]结尾的最大匹配长度
状态转移方程就很简单了:
- 如果
s[i] = '('
,那么dp[i] = 0
; - 否则,我们需要看以字符
s[i-1]
为结尾的最大匹配子串的前面那个字符s[i - dp[i-1] - 1]
是否是左括号:- 若是,说明匹配上了,所以
dp[i] = dp[i-1] + 2 + dp[i - dp[i-1] - 2]
; - 若不是,则没匹配上,
dp[i] = 0
。
- 若是,说明匹配上了,所以
时空复杂度均为O(n)
前面两种思路的空间复杂度均为O(n),此题还有一种常数空间的思路,和前面栈的思想有点类似。
使用两个变量left和right分别用来记录到当前位置时左括号和右括号的出现次数,当遇到左括号时,left自增1;否则right自增1。
- 当
left == right
时,说明匹配成功,此时匹配长度为2*left
; - 一旦遇到
right > left
,说明当前位置不能匹配,则重新开始,即left和right均重置为0。
但是对于"(()"这种时,在遍历结束时左右子括号数都不相等,此时没法更新结果res,但其实正确答案是2,怎么处理这种情况呢?答案是再反向遍历一遍,采取类似的方法,稍有不同的是此时若right < left
了,则重置0。
时间复杂度O(n),空间复杂度O(1)
class Solution {
public:
int longestValidParentheses(string s) {
stack<int>stk;
int res = 0, start = 0;
for(int i = 0; i < s.size(); i++){
if(!stk.empty() && s[stk.top()] == '(' && s[i] == ')') stk.pop();
else stk.push(i);
// 栈顶为当前匹配子串的起始位置
res = max(res, stk.empty() ? i + 1 : i - stk.top());
}
return res;
}
};
class Solution {
public:
int longestValidParentheses(string s) {
vector<int>dp(s.size(), 0);
int res = 0;
for(int i = 1; i < s.size(); i++){
if(s[i] == '(') continue;
int tmp = i - dp[i-1] - 1;
if(tmp >= 0 && s[tmp] == '(')
dp[i] = dp[i-1] + 2 + (tmp >= 1 ? dp[tmp - 1] : 0);
res = max(res, dp[i]);
}
return res;
}
};
class Solution {
public:
int longestValidParentheses(string s) {
int res = 0, left = 0, right = 0, n = s.size();
for (int i = 0; i < n; i++) {
if(s[i] == '(') left++;
else right++;
if (left == right) res = max(res, 2 * right);
else if (right > left) left = right = 0;
}
left = right = 0;
for (int i = n - 1; i >= 0; i--) {
if(s[i] == '(') left++;
else right++;
if (left == right) res = max(res, 2 * left);
else if (left > right) left = right = 0;
}
return res;
}
};