P1944 最长括号匹配

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

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

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

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

string str;
cin >> str;

Part 0

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

Part I

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

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

  2. 如果是右括号,匹配就 pop,\(j\)++;

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

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

退出循环后,此时如果栈是空的,就 \(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

怎么回事?

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

[[]))

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

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

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

然而只能拿 60 分。

Part III

为什么??

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

([((()(((

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

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

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)\)

posted @ 2025-10-05 23:11  masterLazy  阅读(5)  评论(0)    收藏  举报