跳到主要内容

P1944 最长括号匹配

难度算法s日期题目链接
普及/提高−字符串,双指针2024-07-08https://www.luogu.com.cn/problem/P1944

2025-07-17 从洛谷搬到本地,重新格式化了代码,并作若干微调。

这道题写的我很艰难,所以特别想写一个题解详细讲讲。

本文中默认代码中包含以下输入片段:

string str;
cin >> str;

Part 0

括号匹配,肯定要用栈啊。(大概吧。)最简单的思路就是暴力一个一个找,从 i=0i=0 一直找到最后一个元素。但是这样肯定不行,全 TLE,直接爆零。

Part I

这样一个想法:定义两个迭代器 i,ji,j,从 i=0,j=0i=0,j=0 开始“搜索”。循环执行:

  1. 如果 str[j]str[j] 左括号,就 push 到栈中。然后 jj++,继续搜索。

  2. 如果是右括号,匹配就 pop,jj++;

    不匹配,就让 jj 再 ++ 一次,然后令 i=ji=j,准备清空栈,重新开始搜索。清栈之前,如果栈是空的(也就是 iijj 这一段全是匹配的字符串),就令 now=str.substr(i,ji)now=str.substr(i,j-i)。然后

    // 下文给 now 赋值之后都会加上这一句
    // ans 存储的是要求的最长匹配括号串
    if (ans.empty() or now.size() > ans.size()) ans = now;
  3. j>=str.size()j>=str.size(),就退出循环。

退出循环后,此时如果栈是空的,就 now=str.substr(i,ji)now=str.substr(i,j-i)

写成代码就是这样:

stack<char> s;
int i = 0, j = 0;
string ans, now;
while (j < str.size()) {
if (i == j) {
s.push(str[j]);
j++;
} else if (str[j] == '(' or str[j] == '[') {
s.push(str[j]);
j++;
} else {
if (not s.empty() and
(str[j] == ')' and s.top() == '(' or str[j] == ']' and s.top() == '[')) {
s.pop();
j++;
}
//Not ok
else {
if (s.empty()) {
now = str.substr(i, j - i);
if (ans.empty() or now.size() > ans.size())ans = now;
clear(s);
j = i = j + 1;
}
}
}
}
if (s.empty()) {
now = str.substr(i, j - i);
if (ans.empty() or now.size() > ans.size())ans = now;
}
cout << ans << endl;

嗯,很好的想法,然而只能拿 40 分,没有 TLE,全是 WA。

Part II

怎么回事?

仔细分析我们的代码,只有当搜索重新开始时,ii,也就是搜索的起点,就跳到 jj 后面了。但这是不科学的。最简单的例子:

[[]))

在遍历到 “)” 的时候,括号不匹配,但是栈也没空,所以不会设置 nownow。这时 ii 变成 44,这不直接把从 11 开始的 [] 给漏了吗。

解决办法也很简单,就是在括号不匹配,栈也没空的时候,让 ii++,重新搜索。

j=str.size()j=str.size(),跳出循环的时候,我们也让 ii++,重新搜索。

然而只能拿 60 分。

Part III

为什么??

请看这样一个输入(from 这个讨论贴):

([((()(((

按照上面的思路,我们的程序没有输出。为什么?因为在 () 之后,全是 (,一路绿灯就全 push 进去了。等到最后发现栈没空的时候, ii++ 也无济于事。

解决办法也很简单,就是在栈空的时候存一下当前搜索过的(已确认匹配的)字符串就好了。

Part Final

附上完整的代码:

#include <iostream>
#include <string>
#include <stack>
using namespace std;
// ( [ ( ] [ ( ) ] ] ( )
void clear(stack<char>& s) {
while (not s.empty())s.pop();
}
int main() {
string str;
cin >> str;
// I wanna an O(n) algorithm
stack<char> s;// This problem you don't use stack, you can AC?
int i = 0, j = 0;// Iterators, i for begin
string ans, now;
loop:
while (j < str.size()) {
// Begin searching from i
if (i == j) {
s.push(str[j]);
j++;
}
// Left bracket
else if (str[j] == '(' or str[j] == '[') {
s.push(str[j]);
j++;
}
// Right bracket
else {
if (not s.empty() and
(str[j] == ')' and s.top() == '(' or str[j] == ']' and s.top() == '[')) {
s.pop();
j++;
}
// Not ok
else {
if (s.empty()) {
now = str.substr(i, j - i);
if (ans.empty() or now.size() > ans.size())ans = now;
j = i = j + 1;
} else {
clear(s);
i++;
j = i;
goto loop;
}
}
}
// If now stack is empty, save ok string first
if (s.empty()) {
now = str.substr(i, j - i);
if (ans.empty() or now.size() > ans.size())ans = now;
}
}
if (s.empty()) {
now = str.substr(i, j - i);
if (ans.empty() or now.size() > ans.size())ans = now;
} else {
clear(s);
i++;
j = i;
goto loop;
}
cout << ans << endl;
return 0;
}

(用时 162ms 内存 3.10MB)

关于时间复杂度的补充

(2024.07.18)

经过在随机括号串上的测试,本算法的平均时间复杂度约为 O(n)O(n)