32. 最长有效括号 - 力扣(LeetCode)
如果感觉不好理解的话,就用消消乐的思路去思考就知道怎么做了。
栈处理
栈是括号匹配常用的辅助结构了。
维护一个标志位数组,记录不能匹配的括号的下标。一次遍历:
- 如果是左括号:入栈
- 如果是右括号:栈空的话说明该右括号无法匹配,标志位置位;栈非空的话直接出栈即可
最后的标志位数组就是一串0/1的数组,只需要遍历记录最长的连续1的个数就可以了。
class Solution {
public:
int flags[20000];
int longestValidParentheses(string s) {
int n = s.size();
stack<int> stk;//存储的是下标
for(int i = 0; i < n; ++i){
if(s[i] == '(') stk.push(i);//左括号push
else{
if(stk.empty()) flags[i] = 1;
else stk.pop();
}
}
while(!stk.empty()){
flags[stk.top()] = 1;
stk.pop();
}
int res = 0, len = 0;
for(int i = 0; i < n; ++i){
if(flags[i] == 0) ++len;
else{
res = max(res, len);//长度更新
len = 0;
}
}
return max(res, len);//可能需要考虑最后的位
}
};
动态规划
合法子串的结尾必然是右括号 )
,所以状态转移如下:
图来自:动态规划思路详解(c++)——32.最长有效括号 - 最长有效括号 - 力扣(LeetCode)
当s[i]是)
时,dp[i]表示以下标 i 结尾的最长有效子字符串的长度
1、s[i-1] == ‘(’:
那么明显dp[i] = dp[i-2] + 2;
2、s[i-1] == ‘)’:
此时考虑对称性,dp[i-1]为以s[i-1]为结尾的字符串已匹配的长度,如果这一块长度之前那那个字符刚好是 (
,那就可以和s[i]匹配。此时还需要注意上图,i-1-dp[i-1]处的字符即为和s[i]匹配的字符,我们累加长度的时候要把匹配字符的前一个位置i-2-dp[i-1]也加上,这样才能取得最大值。
不匹配的情况直接为0就可以,初始化均为0即可。
可以使用哨兵的思路简化下标处理:
class Solution {
public:
int dp[20000];
int longestValidParentheses(string s) {
s = "-" + s;
int n = s.size(), res = 0;
for(int i = 2; i < n; ++i){
if(s[i] == ')'){//合法的子串一定以右括号结尾
if(s[i-1] == '(') dp[i] = dp[i-2] + 2;
else if(s[i-1-dp[i-1]] == '(') dp[i] = dp[i-1] + dp[i-dp[i-1]-2] + 2;
res = max(res, dp[i]);
}
}
return res;
}
};
当然不用也可以,只是下标会看起来比较乱:
class Solution {
public:
int dp[20000];
int longestValidParentheses(string s) {
int n = s.size();
for(int i = 1; i < n; ++i){
if(s[i] == ')'){//合法的子串一定以右括号结尾
if(s[i-1] == '(') dp[i+1] = dp[i-1] + 2;
else if(i-1-dp[i] >= 0 && s[i-1-dp[i]] == '(') dp[i+1] = dp[i] + dp[i-1-dp[i]] + 2;
}
}
return *max_element(dp+1, dp+n+1);
}
};