巧解括号序列分解问题:栈思想的轻量实现

巧解括号序列分解问题:栈思想的轻量实现

在算法世界里,括号序列相关问题是经典的基础题型,而将括号序列拆分为独立部分并去除各部分最外层括号这一问题,更是考察对栈思想灵活运用的典型代表。很多同学初遇此类问题会下意识想到用栈结构实现,但其实抓住问题本质,仅通过一个计数变量就能完成轻量求解,既简化代码又提升效率。今天就带大家深度拆解这道题,从原理分析到代码实现,吃透栈思想的核心运用✨

一、问题剖析:读懂题目核心要求

我们先明确这道题的核心需求:

给定一个合法的括号序列(仅包含()),需要先将其拆分为若干个独立的合法括号子序列 ,再去掉每个独立子序列的最外层一对括号,最后将所有剩余部分拼接起来,得到最终结果。

举个例子更直观

假设输入的括号序列为((()))(()),我们对其进行拆解与处理:

  1. 拆分独立子序列:该序列可拆分为((()))(())两个独立的合法括号部分;

  2. 去除最外层括号:((()))去除外层后为(())(())去除外层后为()

  3. 拼接剩余部分:最终结果为(())()

这道题的关键难点在于如何精准判断独立的合法括号子序列,而这正是栈思想能发挥作用的地方,且我们无需实现完整的栈,仅用计数法就能模拟栈的核心逻辑💡

二、算法原理:栈思想的本质------计数模拟

2.1 栈思想的核心逻辑

对于仅包含一种括号的合法序列,判断其合法性的核心是左括号与右括号的数量平衡。传统栈实现的思路是:遇到左括号入栈,遇到右括号出栈,栈空时说明当前是一个合法的括号序列。

但这道题中,我们无需实际创建栈结构,因为栈的深度等价于左括号与右括号的数量差值

  • 遇到左括号,差值**+1**(相当于栈顶指针上移,栈深度增加);

  • 遇到右括号,差值**-1**(相当于栈顶指针下移,栈深度减少);

  • 当差值等于0 时,说明当前从某一起点到当前位置的括号序列是独立且合法的(相当于栈空,完成一次匹配)。

这个差值计数变量,就是栈顶指针的"轻量替身",让我们用O(1)的空间复杂度实现了栈的核心功能。

2.2 算法步骤可视化

为了更清晰理解计数法的执行过程,我们以((()))(())为例,用步骤图展示整个拆解过程(Pre为当前独立子序列的起始位置,初始值为0;count为左右括号差值,初始值为0):

Plain 复制代码
输入序列:( ( ( ) ) ) ( ( ) )
下标索引:0 1 2 3 4 5 6 7 8 9
-----------------------------------
步骤1:索引0,字符( → count=1 → 差值≠0,继续
步骤2:索引1,字符( → count=2 → 差值≠0,继续
步骤3:索引2,字符( → count=3 → 差值≠0,继续
步骤4:索引3,字符) → count=2 → 差值≠0,继续
步骤5:索引4,字符) → count=1 → 差值≠0,继续
步骤6:索引5,字符) → count=0 → 差值=0!
       👉 独立子序列:0~5 → ((()))
       👉 去除外层:1~4 → (())
       👉 更新Pre=6(下一段起始位置)
-----------------------------------
步骤7:索引6,字符( → count=1 → 差值≠0,继续
步骤8:索引7,字符( → count=2 → 差值≠0,继续
步骤9:索引8,字符) → count=1 → 差值≠0,继续
步骤10:索引9,字符) → count=0 → 差值=0!
        👉 独立子序列:6~9 → (())
        👉 去除外层:7~8 → ()
        👉 更新Pre=10(遍历结束)
-----------------------------------
最终拼接:(()) + () = (())()

2.3 核心原则总结

  1. 计数规则:左括号+1,右括号-1,全程count≥0(因输入是合法序列);

  2. 独立判定:count=0时,Pre到当前索引为一个独立合法子序列;

  3. 外层去除 :独立子序列的**首字符(Pre)尾字符(当前索引)**为最外层括号,截取中间部分即可;

  4. 起始更新 :处理完一个独立子序列后,将Pre更新为当前索引+1,作为下一段的起始位置。

三、C++代码实现:轻量高效,逐行解析

基于上述原理,我们编写C++代码,核心思路是一次遍历+计数判断+字符串截取,时间复杂度O(n)(n为括号序列长度,仅遍历一次),空间复杂度O(1)(仅使用有限变量,结果字符串除外)。

3.1 完整核心代码

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

string removeOuterParentheses(string s) {
    string ret; // 存储最终结果
    int pre = 0; // 记录当前独立子序列的起始位置
    int count = 0; // 左右括号差值,模拟栈顶指针
    for (int i = 0; i < s.size(); ++i) {
        // 计数规则:左括号+1,右括号-1
        if (s[i] == '(') count++;
        else count--;
        
        // 差值为0,找到独立合法子序列
        if (count == 0) {
            // 截取中间部分:pre+1 到 i-1,长度为i-pre-1
            ret += s.substr(pre + 1, i - pre - 1);
            pre = i + 1; // 更新下一段起始位置
        }
    }
    return ret;
}

// 测试主函数
int main() {
    string s = "((()))(())";
    cout << "输入括号序列:" << s << endl;
    cout << "处理后结果:" << removeOuterParentheses(s) << endl;
    return 0;
}

3.2 关键代码逐行解析

(1)变量初始化

cpp 复制代码
string ret; // 结果字符串,拼接各部分处理后的内容
int pre = 0; // 初始起始位置为0,指向第一个字符
int count = 0; // 差值初始为0,相当于栈为空

这三个变量是核心,无额外容器,实现轻量求解。

(2)遍历与计数

cpp 复制代码
for (int i = 0; i < s.size(); ++i) {
    if (s[i] == '(') count++;
    else count--;
    // ...
}

遍历整个括号序列,按规则更新count,这一步是对栈入栈、出栈操作的轻量模拟,时间复杂度O(n)。

(3)独立子序列判定与截取

cpp 复制代码
if (count == 0) {
    ret += s.substr(pre + 1, i - pre - 1);
    pre = i + 1;
}

这是代码的核心逻辑行,重点解析:

  • s.substr(pos, len):C++中字符串截取函数,pos为起始下标,len为截取长度;

  • pre + 1:跳过当前独立子序列的最外层左括号;

  • i - pre - 1:截取长度为"子序列总长度-2"(去掉首尾两个外层括号),总长度为i - pre + 1,因此(i - pre + 1) - 2 = i - pre - 1

  • pre = i + 1:处理完当前段,将起始位置更新为下一段的第一个字符,为后续遍历做准备。

(4)返回结果

遍历结束后,ret中已拼接好所有去除外层括号后的部分,直接返回即可。

3.3 测试结果

输入:((()))(())

输出:(())()

与我们之前的手动推导结果一致,代码运行正确✅

四、算法优化与拓展思考

4.1 性能分析

  • 时间复杂度:O(n),仅对括号序列进行一次遍历,字符串截取和拼接的总操作数也为O(n);

  • 空间复杂度 :O(1),除了存储结果的字符串ret,仅使用了precounti三个整型变量,无额外的栈、数组等容器,相比传统栈实现更节省空间。

4.2 拓展场景

这道题的核心是单种括号的合法性判断与拆分 ,如果题目拓展为多种括号(()[]{}),还能使用计数法吗?

答案是不能 ,因为多种括号需要严格匹配类型(如[)是非法的),此时计数法无法区分括号类型,必须使用真实的栈结构,将左括号入栈,遇到右括号时判断栈顶是否为对应左括号,匹配则出栈,不匹配则序列非法。

这也印证了:计数法是栈思想在单种括号场景下的特化优化,抓住问题本质才能选择最优解法。

4.3 解题思路升华

从这道题我们能总结出一个通用的算法解题思路:

先理解数据结构的核心思想,再根据问题场景做特化优化,避免生搬硬套数据结构

栈的核心是"先进后出"和"匹配判定",在单种括号问题中,其匹配判定的核心可通过计数法替代,这就是对栈思想的深度理解,而非单纯的栈结构使用。

五、总结

本文围绕括号序列分解并去除外层括号 问题,从题目剖析到原理讲解,再到代码实现,层层递进地讲解了栈思想的轻量实现------计数法的核心运用:

  1. 问题核心是独立合法括号子序列的判定,利用左括号与右括号的数量差值可实现轻量判断;

  2. 计数法是栈思想的特化优化,差值为0等价于栈空,实现O(1)空间复杂度;

  3. C++代码通过一次遍历+计数判断+字符串截取实现,简洁高效,易理解易实现;

  4. 解题的关键是抓住数据结构的核心思想,而非生搬硬套,单种括号用计数法,多种括号用真实栈。

希望这篇文章能帮助大家吃透括号序列问题的解题思路,更能理解"透过现象看本质"的算法解题思维📚 后续也会继续分享更多经典算法题的拆解与优化,一起在算法世界里打怪升级~

相关推荐
Just right2 小时前
重学算法 数组 LC27移除元素
数据结构·算法
Jasmine_llq2 小时前
《B4496 [GESP202603 一级] 数字替换》
数据结构·字符串遍历算法·字符替换算法·条件判断算法·字符串输入输出算法·顺序处理算法·批量字符修改算法
代码改善世界2 小时前
【C++初阶】string类(一):从基础到实战
开发语言·c++
计算机安禾2 小时前
【数据结构与算法】第15篇:队列(二):链式队列的实现与应用
c语言·开发语言·数据结构·c++·学习·算法·visual studio
Leventure_轩先生2 小时前
[RL]强化学习指导搭建IC2E核反应堆
开发语言·php
迷途之人不知返2 小时前
初次学习模板
c++
算法鑫探2 小时前
C语言密码验证:3次机会解锁
c语言·数据结构·算法·新人首发