每日一题,坚持使我强大
今日份快乐: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] ,要算上前一个匹配字符串的长度。
解释一下
1 2 3 4 5 6 7 8 ( ) ( { [ ] } ) 上表中,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;
}
欢迎大佬留言指错,哪里讲的不清楚也欢迎留言提出