给定一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长的包含有效括号的子串的长度。
示例 1:
输入: "(()"
输出: 2
解释: 最长有效括号子串为 "()"
示例 2:
输入: ")()())"
输出: 4
解释: 最长有效括号子串为 "()()"
思路分析: 刚开始拿到这道题,我想起了之前做过的括号匹配问题,使用栈来进行辅助求解。使用一个指针,从前往后进行扫描,如果是左括号,直接放入栈中,如果是右括号,则分栈为空、不为空两种情况进行处理。如果栈为空,则此次子串寻找完毕,进行结算长度、更新最长结果,并且将右括号放入栈中,如果栈不为空,判断栈顶是否为左括号,然后进行更新中间结果。
不通过的代码如下
int longestValidParentheses(string s) {
stack<char> myStack;
int strLen = s.size();
int beforeSize = 0;//上一次计算结果时栈的大小
int maxResult = 0;//最大的结果
int beforeResult = 0;//上一次的结果
int tempResult = 0;//
for (int i = 0; i < strLen; ++i){
if (s[i] == '('){//左括号直接进栈
myStack.push(s[i]);
continue;
}
if (!myStack.empty() && myStack.top() == '('){//栈顶为左括号,出栈
myStack.pop();
tempResult += 2;
continue;
}
//更新上一次的结果
if (myStack.size() < beforeSize){
beforeResult += tempResult;
}
else {
beforeResult = tempResult;
}
//更新上一次计算结果的栈大小为本次计算结果的栈大小
beforeSize = myStack.size();
//更新最大结果
if (beforeResult > maxResult){
maxResult = beforeResult;
}
myStack.push(s[i]);
tempResult = 0;//计算结果后清零
}
if (beforeResult == 0){//如果一直都没有计算
maxResult = tempResult;
}
return maxResult;
}
不通过的一个典型示例为“()(()”,这个示例跑出来的结果为4,此算法虽然考虑到了如何合并两个相邻的括号子串,但是计算结果的操作都是出现右括号的时候进行结算,且tempResult的计数不会将两个子串进行分开,只会在计算一次结果后进行清零(出现右括号),否则只会累加。。。
既然这种算法的tempResult计算有问题,所以我换了一种思路,使用两个指针从前往后遍历,依次定位“()”,并且进行扩展子串,扩展子串分三种情况。
然而这种算法也不通过。。。
int longestValidParentheses(string s) {
int resultMaxLen = 0;
int beforeSubStrLen = 0;//上一次的结果
int beforeSubStrBegin = -1;//上一次结果的起始端
int beforeSubStrEnd = -1;//上一次结果的尾端
int strLength = s.size();
int leftBeginIndex = 0;//括号子串左端
int rightBeginIndex = 1;//括号子串右端
//以括号子串的连个初始为基准扫描
while (rightBeginIndex < strLength){
//如果基准定位到了一对括号
if (s[leftBeginIndex] == '(' && s[rightBeginIndex] == ')'){
int leftIndex = leftBeginIndex;//以初始为基准的匹配成功的子串的左右
int rightIndex = rightBeginIndex;
//分三种情况进行扩充子串
while (leftIndex > beforeSubStrEnd && rightIndex < strLength){
if (leftIndex > 0 && rightIndex < strLength - 1 && s[leftIndex - 1] == '(' && s[rightIndex + 1] == ')'){
--leftIndex;//第一种情况,子串左边添加左括号,子串右边添加右括号
++rightIndex;
}
else if (rightIndex < strLength - 2 && s[rightIndex + 1] == '(' && s[rightIndex + 2] == ')'){
rightIndex += 2;//第三种情况,子串右边添加一对括号
}
else if (leftIndex > beforeSubStrEnd + 2 && s[leftIndex - 2] == '(' && s[leftIndex - 1] == ')'){
leftIndex -= 2;//第二种情况,子串左边添加一对括号
}
else {
break;
}
}
//更新上一次的结果
int tempResult = rightIndex - leftIndex + 1;
if (beforeSubStrEnd + 1 == leftIndex){//如果此次子串与上一次的相邻,则证明上一次与本次子串可以合并
beforeSubStrLen += tempResult;//上一次的结果更新加上本次的结果
beforeSubStrEnd = rightIndex;//上一次的结果尾端更新为本次结果的尾端
if (beforeSubStrBegin == -1){//更新上一次的结果前端
beforeSubStrBegin = leftIndex;
}
}
else{//如果上一次与本次无法合并
beforeSubStrLen = tempResult;//更新上一次结果为本次结果
beforeSubStrEnd = rightIndex;//更新上一次结果的尾端为本次结果的尾端
beforeSubStrBegin = leftIndex;//更新上一次结果的前端
}
while (beforeSubStrBegin > 0 && beforeSubStrEnd < strLength - 1 && s[beforeSubStrBegin - 1] == '(' && s[beforeSubStrEnd + 1] == ')'){
--beforeSubStrBegin;
++beforeSubStrEnd;
}
tempResult = beforeSubStrEnd - beforeSubStrBegin + 1;
beforeSubStrLen = tempResult;
if (beforeSubStrLen > resultMaxLen){//更新最大结果
resultMaxLen = beforeSubStrLen;
}
leftBeginIndex = beforeSubStrEnd + 1;//指针得移动到本次子串的后端
rightBeginIndex = beforeSubStrEnd + 2;//防止重复扫描的问题
}
else {
++leftBeginIndex;
++rightBeginIndex;
}
}
return resultMaxLen;
}
算法缺陷在于只考虑到了部分的子串合并情况,以及对“上一次结果”的储存、处理存在缺陷。。。
经查阅前辈的博客,https://blog.csdn.net/weixin_38823568/article/details/80997966
算法一:利用栈进行辅助
解题思路:
1.需有一个变量start记录有效括号子串的起始下标,max表示最长有效括号子串长度,初始值均为0
2.遍历给字符串中的所有字符
2.1若当前字符s[index]为左括号'(',将当前字符下标index入栈(下标稍后有其他用处),处理下一字符
2.2若当前字符s[index]为右括号')',判断当前栈是否为空
2.2.1若栈为空,则start = index + 1,处理下一字符(当前字符右括号下标不入栈)
2.2.2若栈不为空,则出栈(由于仅左括号入栈,则出栈元素对应的字符一定为左括号,可与当前字符右括号配对),判断栈是否为空
2.2.2.1若栈为空,则max = max(max, index-start+1)
2.2.2.2若栈不为空,则max = max(max, index-栈顶元素值)
int longestValidParentheses(string s) {
int maxResult = 0, start = 0;
int len = s.length();
if (len == 0) return 0;
stack<int> myStack;
for (int index = 0; index < len; index++){
//遇左括号(,压栈(栈中元素为当前位置所处的下标)
if ('(' == s[index]){
myStack.push(index);
continue;
}
else {
if (myStack.empty()){
start = index + 1;
continue;
}
else {
myStack.pop();
if (myStack.empty()){
maxResult = max(maxResult, index - start + 1);
}
else {
maxResult = max(maxResult, index - myStack.top());
}
}
}
}
return maxResult;
}
他的算法是将左括号的下标入栈,很好的解决了如何处理子串合并、子串长度计算和更新的问题。
算法二:使用动态规划
需用到辅助数组d[s.length()],表示从当前字符开始,到字符串结尾的最长有效括号子串长度(当前字符需为有效括号子串的第一个字符)
解题思路:从字符串结尾往前处理,求辅助数组d[]
当前字符下标为index,若当前字符为左括号’(’,判断index+1+d[index+1]位置的字符是否为右括号’)’,若为右括号,则d[index] = d[index+1]+2,并且判断index+1+d[index+1]+1位置的元素是否存在,若存在,则d[index] += d[index+1+d[index+1]+1](解决上述两个有效括号子串直接相邻的情况)
int longestValidParentheses(string s) {
int strSize = s.size();
if (strSize == 0){
return 0;
}
int maxLength = 0;
vector<int> dp(strSize, 0);//用于储存以i下标为起始的最长子串的长度
for (int index = strSize - 2; index >= 0; --index){
int right = index + 1 + dp[index + 1];//以index为左括号与其匹配的右括号的下标
if ('(' == s[index] && right < strSize && ')' == s[right]){//匹配成功
dp[index] = dp[index + 1] + 2;//首先加上index+1的长度
if (right + 1 < strSize){//如果子串右边还在s串的下标范围内,还需要进行合并子串
dp[index] += dp[right + 1]
}
if (dp[index] > maxLength){//更新最长子串
maxLength = dp[index];
}
}
}
return maxLength;
}
此算法从后往前进行扫描,很好的解决了合并子串的问题,并且利用dp辅助数组,解决了如何储存上一次的子串结果。
惭愧,惭愧。