【每日一题】codeforces 5C (括号匹配 + dp)

每日一题,坚持使我强大


今日份快乐:codeforces 5C 传送门
明天份快乐:codeforces 547B 传送门


题目大意

给出一个只有 ‘(’ 和 ‘)’ 的括号字符串,问这个括号字符串的最长匹配子串的长度和出现次数
括号匹配的规则:( ) , (()) , ()() 和 (()()) 都是匹配的字符串,他们互相嵌套得到的字符串也是匹配的
换个说法:在一个括号字符串中不断删去一对括号,并且不断构成新串。如果最终字符串被删完,则就是一个匹配的字符串。
eg:(()(())) →→ ( (())) →→ ( ( )) →→ ( ) →→ null(空) 这就是一个匹配的括号字符串

分析

括号匹配是一个经典的栈的应用问题。


栈(stack):是一种线性存储结,它储存数据的特点为 “先进后出” ,如下图 (卑劣的画技)在这里插入图片描述
我们先把 1,2,3 放入栈中,这个过程叫进栈,对应的函数为 push(元素)。这时,一号元素在栈底,三号元素在栈顶。
再把栈顶元素给拿出去,这个过程叫出栈,对应的函数为 pop()。但这个函数不会返回栈顶元素的值,如果要得到栈顶元素的值,要用top()函数。


下边便是如何用栈来判断括号匹配:
对于括号序列:( { 【()】} 【】{()} )
我们定义一个指针 ind ,从左到右的扫描序列。
当碰到左括号时,把左括号放入栈中。
当碰到右括号时,我们取出栈顶,如果这时栈顶元素和 ind 指向的括号配对,则弹出栈顶,继续扫描。如果不匹配,这个序列就是不匹配的括号字符串。
如果 ind 扫描完以后,栈中还有元素,这个字符串是不匹配的,反则为匹配。
过程图:
step 1 : ( { 【( 依次入栈,( 为栈顶,这时栈内有( { 【(
step 2 :扫描到 ‘)’ 与栈顶匹配,弹出栈顶,这时栈内有 ( { 【
step 3 :扫描到 ‘】’ 与栈顶匹配,弹出栈顶,这时栈内有 ( {
step 4 :扫描到 ‘}’ 与栈顶匹配,弹出栈顶,这时栈内有 (
step 5 :扫描到 ‘【 ’,入栈,这时栈内有 ( 【
step 6 :扫描到 ‘】’ 与栈顶匹配,弹出栈顶,这时栈内有 (
step 7:扫描到 ‘{ ’, 入栈,这时栈内有 ( {
step 8:扫描到 ‘( ’,入栈,这时栈内有 ( {(
step 9:扫描到 ‘)’ 与栈顶匹配,弹出栈顶,这时栈内有( {
step 10:扫描到 ‘}’ 与栈顶匹配,弹出栈顶,这时栈内有(
step 11:扫描到 ‘)’ 与栈顶匹配,弹出栈顶,这时栈内为空
step 12:扫描完成,栈为空,则字符串匹配

可以去思考一下,什么时候栈不为空,((()。


回到本题:
本题只有 ‘(’ 和 ‘)’ ,但要找到最长的子串,还要出现的次数
我们可以这样解决,因为只有 ‘(’ 和 ‘)’ ,我们便不用纪录入栈的是什么括号,我们便可纪录成 ‘(’ 出现的位置 x,而当前 ‘)’ 的位置为 ind,则 ind - x + 1 便是这一段匹配的括号的长度。
我们定义dp[i]中存的是:当前位置匹配字符串的最长长度。
如果 s[i] 为左括号,则 dp[i] = 0。
如果 s[i] 为右括号,则 dp[i] = i - x + 1。 这里考虑一个问题,如果对于 (()())() 这样的字符串,最后一个 ‘)’ 匹配到的为倒数第二个位置 x 的 ‘(’,但是 x - 1 位置右括号所在的子串也是匹配的,因为dp[i]为最长的匹配字符串,则 dp[i] = i - x + 1 + dp[x-1] ,要算上前一个匹配字符串的长度。

解释一下

12345678
 (  )  ( {[]} ) 

上表中,1 到 2 是一个匹配的字符串,3 到 8 也是要给匹配的字符串。很显然,这两个相邻的字符串也可以拼成一个更长的匹配的字符串,故而,再计算最长匹配字符串长度时,要加上当前匹配串旁边的匹配字符串的长度(如果前一个字符串不匹配,可以认为是一个长度为 0 的匹配字符串)。

对于最长长度和数量,我们开一个 mx 和 num 来纪录就ok。

代码

#include <bits/stdc++.h>
#include <stack>
using namespace std;

const int maxn = 1e6+5;
int dp[maxn] = {0};

int main() {

	ios::sync_with_stdio(false);
	
	string s;
	cin >> s;
	
	int len = s.size();
	stack<int>st;
	s = " " + s; 		// 错开一位,避免 i - 1 越界
	int mx = 0, num = 1;	// 纪录最大值 和 次数
	for(int i = 1; i <= len; i++){		// 扫描
		if(s[i] == '('){		// 左括号位置入栈
			st.push(i);
		}
		else if(!st.empty()){		// 右括号,并且栈不为空
			int ind = st.top();	// 取出匹配到的左括号的位置
			st.pop();		// 弹出栈顶元素,匹配成功就弹出
			dp[i] = i - ind + 1 + dp[ind-1];	// 纪录长度
			if(dp[i] == mx) num++;			// 更新数量
			if(dp[i] > mx) mx = dp[i], num = 1;	// 更新最值
		}
	}
	
	cout << mx << " " << num << endl;	// 注意,没有匹配字符串时输出 0 1,我们可以直接定义 num = 1,当没有匹配字符串时,num 不会被修改
	
    return 0;
}

欢迎大佬留言指错,哪里讲的不清楚也欢迎留言提出

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值