32. 最长有效括号
题目:
给你一个只包含 '('
和 ')'
的字符串,找出最长有效(格式正确且连续)括号子串的长度。
示例 1:
输入:s = "(()"
输出:2
解释:最长有效括号子串是 "()"
示例2:
输入:s = ")()())"
输出:4
解释:最长有效括号子串是 "()()"
示例3:
输入:s = ""
输出:0
提示:
0 <= s.length <= 3 * 104
-
s[i]
为'('
或')'
解答方法1
首先摆在最前面的是动态规划的方案,且是一个一维的dp表。dp[s.length]中每一个都代表从上一个截断有效字符的位置起到当前位置的字符最长的连续括号子串的长度,因此只要遍历一次,一边遍历一边记录一个最大的长度,最大长度即答案,返回即可
具体代码如下:
int longestValidParentheses(string s){
//1:洗掉无效参数
if(s.length() == 0 || s.length() == 1) {
return 0;
}
//初始化dp表格
vector<int> dp ;
for(int i = 0;i<s.length();i++){
dp.push_back(0);
}
int currentLong = 0;
//遍历字符,找到其中的所有有效连续字符,并记录最长的
for(int i = 1;i<s.length();i++){
char ITem = s[i];
if('(' == ITem){
dp[i] =0;
continue;
}
// 前一个是(
if(s[i-1] == '('){
if(i -2 >=0){
dp[i]=2 + dp[i-2];
} else{
dp[i] = 2;
}
}
//前一个是 )
if(s[i-1]== ')' ){
if(i-dp[i-1] -1 >0 && s[i-dp[i-1] -1] == '('){
dp[i] =dp[i-1] +2 + dp[i-dp[i-1] -2];
} else if(i-dp[i-1] -1 == 0 && s[i-dp[i-1] -1] == '('){
dp[i] =dp[i-1] +2 ;
} else{
dp[i] = 0;
}
}
currentLong = currentLong <dp[i] ?dp[i] :currentLong;
}
return currentLong;
}
解答方法2
看到括号匹配的问题,还会让人想到的一种可能解法就是使用栈,左括号进栈,右括号出栈,出栈的时候记录长度,根据栈的长度控制右括号字符截断的连续括号字符,但是这种通用思路解决不了一种场景,类似这样的字符“()(((()”,因为无法识别栈中左括号截断的场景,因此进栈出栈不能使用 ‘(’ 和‘ )’来做入栈和出栈使用,但是因为本体最后计算的是最长有效括号,可使用字符的下标作为来出栈入栈,简单来说如下:
0:为防止空栈越界,在遍历字符开始之前先入栈一个 -1,作为栈底
1:如果是‘(’,将左括号在原字符中下标入栈
2:如果是‘)’,将前面匹配的左括号的下标出栈,更新最长子串长度
3:如果是‘)’,该次循环栈顶元素是右括号,则更新栈顶元素,持续最长字符长度重新计算
具体代码 如下:
int longestValidParentheses_stack(string s){
if(s.length() == 0 || s.length() == 1) {
return 0;
}
stack<int>strIndexStack;
strIndexStack.push(-1);
int continuedLongest = 0;
for(int i =0 ;i<s.length();i++){
if(s[i] == '('){
strIndexStack.push(i);
continue;
}
if(s[i] == ')'){
int index = strIndexStack.top();
if(index < 0 || s[index] ==')' ){
strIndexStack.pop();
strIndexStack.push(i);
continue;
}
if(s[index] == '('){
strIndexStack.pop();
continuedLongest =i-strIndexStack.top() > continuedLongest ? i-strIndexStack.top() :continuedLongest;
}
}
}
return continuedLongest;
}
解答方法3
第三种解法使用的是本题独特场景才能使用的技巧型方案,主要就是使用分别技术左右括号来计数最长有效括号,具体思路如下:
1:从左到右遍历字符,左括号leftCount+1;右括号rightCount+1,每次循环体结束时检查leftcount和rightCount值是否相等,相等的话就是当前最长子串,更新最长子串的长度maxLong,rightCount大于leftCount,则从新开始计数
2:和步骤一一样,换成从右往左遍历,左括号leftCount+1;右括号rightCount+1,每次循环体结束时检查leftcount和rightCount值是否相等,相等的话就是当前最长子串,更新最长子串的长度maxLong,leftcount大于rightcount重新开始计数
3:从左到右的时候无法识别类似”()(((()()“的最长子串,从右到左无法识别”()())))))()“最长子串的场景,因此将字符串从左往右检查一遍,在从右向左检查一遍,将字符中最长子串的场景全部识别
代码如下:
int longestValidParentheses_computer(string s){
int leftCount = 0;
int rightCount = 0;
int maxLong = 0;
for(int i = 0;i<s.length();i++){
if(s[i] == '('){
leftCount ++;
} else{
rightCount++;
}
if(rightCount == leftCount){
maxLong = leftCount *2 > maxLong ? leftCount *2:maxLong;
} else if(rightCount > leftCount){
leftCount=0;
rightCount=0;
}
}
leftCount = rightCount = 0;
for(int i = s.length()-1;i>=0;i--){
if(s[i] == '('){
leftCount ++;
} else{
rightCount++;
}
if(rightCount == leftCount){
maxLong = leftCount *2 > maxLong ? leftCount *2:maxLong;
} else if(leftCount >rightCount){
leftCount=0;
rightCount=0;
}
}
return maxLong;
}
算法复杂度分析:
解法1和解法2的复杂度是一样的,全部如下:
时间复杂度:O(n),时间复杂度是因为全部对字符进行一次全遍历
空间复杂度:O(n),空间复杂度是因为动态规划采用了N个大小的空间存储,栈的方法,在最差的情况下也会使用N个大小的空间,因此也是O(n)的复杂度
解法3的复杂度:
时间复杂度:O(N),时间复杂度其实是O(2*n),因为遍历了2次字符,但是计算时间复杂度一般不会关心常数项,因此也是O(n)的时间复杂度
空间复杂度:O(1),全程只采用了3个整型变量来存储,即采用了有限了变量来存储,所以空间复杂度为O(1)