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

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

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

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

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

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

举个例子更直观

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

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

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

相关推荐
三毛的二哥1 小时前
BEV:典型BEV算法总结
人工智能·算法·计算机视觉·3d
2401_873479401 小时前
如何利用IP查询定位识别电商刷单?4个关键指标+工具配置方案
开发语言·tcp/ip·php
我爱cope1 小时前
【从0开始学设计模式-10| 装饰模式】
java·开发语言·设计模式
菜鸟学Python1 小时前
Python生态在悄悄改变:FastAPI全面反超,Django和Flask还行吗?
开发语言·python·django·flask·fastapi
南宫萧幕2 小时前
自控PID+MATLAB仿真+混动P0/P1/P2/P3/P4构型
算法·机器学习·matlab·simulink·控制·pid
测试19982 小时前
2026最新软件测试面试八股文【附文档】
自动化测试·软件测试·python·测试工具·面试·职场和发展·测试用例
zmsofts2 小时前
java面试必问13:MyBatis 一级缓存、二级缓存:从原理到脏数据,一篇讲透
java·面试·mybatis
浪浪小洋2 小时前
c++ qt课设定制
开发语言·c++
charlie1145141913 小时前
嵌入式C++工程实践第16篇:第四次重构 —— LED模板,从通用GPIO到专用抽象
c语言·开发语言·c++·驱动开发·嵌入式硬件·重构
handler013 小时前
Linux: 基本指令知识点(2)
linux·服务器·c语言·c++·笔记·学习