LeetCode 最长的有效括号(栈、动态规划)

43 篇文章 2 订阅
20 篇文章 0 订阅

给定一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长的包含有效括号的子串的长度。

示例 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辅助数组,解决了如何储存上一次的子串结果。

惭愧,惭愧。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值