巧解括号序列分解问题:栈思想的轻量实现
- 一、问题剖析:读懂题目核心要求
- 二、算法原理:栈思想的本质------计数模拟
-
- [2.1 栈思想的核心逻辑](#2.1 栈思想的核心逻辑)
- [2.2 算法步骤可视化](#2.2 算法步骤可视化)
- [2.3 核心原则总结](#2.3 核心原则总结)
- 三、C++代码实现:轻量高效,逐行解析
-
- [3.1 完整核心代码](#3.1 完整核心代码)
- [3.2 关键代码逐行解析](#3.2 关键代码逐行解析)
- [3.3 测试结果](#3.3 测试结果)
- 四、算法优化与拓展思考
-
- [4.1 性能分析](#4.1 性能分析)
- [4.2 拓展场景](#4.2 拓展场景)
- [4.3 解题思路升华](#4.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,全程count≥0(因输入是合法序列);
-
独立判定:count=0时,Pre到当前索引为一个独立合法子序列;
-
外层去除 :独立子序列的**首字符(Pre)和尾字符(当前索引)**为最外层括号,截取中间部分即可;
-
起始更新 :处理完一个独立子序列后,将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,仅使用了pre、count、i三个整型变量,无额外的栈、数组等容器,相比传统栈实现更节省空间。
4.2 拓展场景
这道题的核心是单种括号的合法性判断与拆分 ,如果题目拓展为多种括号(()[]{}),还能使用计数法吗?
答案是不能 ,因为多种括号需要严格匹配类型(如[)是非法的),此时计数法无法区分括号类型,必须使用真实的栈结构,将左括号入栈,遇到右括号时判断栈顶是否为对应左括号,匹配则出栈,不匹配则序列非法。
这也印证了:计数法是栈思想在单种括号场景下的特化优化,抓住问题本质才能选择最优解法。
4.3 解题思路升华
从这道题我们能总结出一个通用的算法解题思路:
先理解数据结构的核心思想,再根据问题场景做特化优化,避免生搬硬套数据结构。
栈的核心是"先进后出"和"匹配判定",在单种括号问题中,其匹配判定的核心可通过计数法替代,这就是对栈思想的深度理解,而非单纯的栈结构使用。
五、总结
本文围绕括号序列分解并去除外层括号 问题,从题目剖析到原理讲解,再到代码实现,层层递进地讲解了栈思想的轻量实现------计数法的核心运用:
-
问题核心是独立合法括号子序列的判定,利用左括号与右括号的数量差值可实现轻量判断;
-
计数法是栈思想的特化优化,差值为0等价于栈空,实现O(1)空间复杂度;
-
C++代码通过一次遍历+计数判断+字符串截取实现,简洁高效,易理解易实现;
-
解题的关键是抓住数据结构的核心思想,而非生搬硬套,单种括号用计数法,多种括号用真实栈。

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