【算法题】栈

栈是一种"后进先出(LIFO)"的线性数据结构,核心优势是高效处理"匹配、抵消、嵌套"类问题。在字符串处理(去重、回退、解码)、表达式计算、栈序列验证等场景中,栈能将复杂的顺序逻辑简化为直观的入栈/出栈操作。本文通过5道经典题目,拆解栈在不同场景下的解题思路与代码实现。

一、删除字符串中的所有相邻重复项

题目描述:

给定字符串 s,反复删除相邻且相同的字符对,直到无法再删除,返回最终字符串(删除操作会重复进行,直到没有相邻重复项)。

示例

  • 输入:s = "abbaca",输出:"ca""bb" 删去→"aaca""aa" 删去→"ca"

解题思路:

用栈模拟"相邻抵消"过程:

  1. 遍历字符串的每个字符:
    • 若栈不为空且栈顶字符与当前字符相同,弹出栈顶(抵消重复项);
    • 若栈为空或字符不同,将当前字符入栈。
  2. 遍历结束后,栈中剩余字符即为结果。

完整代码:

cpp 复制代码
class Solution {
public:
    string removeDuplicates(string s) {
        string ret; // 用string模拟栈,效率更高
        for(auto ch : s)
        {
            if(ret.size() && ch == ret.back())
                ret.pop_back();
            else 
                ret.push_back(ch);
        }
        return ret;
    }
};

复杂度分析:

  • 时间复杂度:O(n)O(n)O(n),n 为字符串长度,每个字符最多入栈/出栈一次。
  • 空间复杂度:O(n)O(n)O(n),最坏情况(无重复字符)栈存储所有字符。

二、比较含退格的字符串

题目描述:

给定两个字符串 st,其中 '#' 表示退格键(删除前一个字符),判断处理后的两个字符串是否相等。

示例

  • 输入:s = "ab#c", t = "ad#c",输出:true(均处理为 "ac"
  • 输入:s = "a#c", t = "b",输出:false(处理后分别为 "c""b"

解题思路:

用栈模拟"退格"过程,分别处理两个字符串后比较结果:

  1. 定义辅助函数 changeStr:遍历字符串,遇到非 '#' 字符入栈,遇到 '#' 且栈非空则出栈。
  2. 分别处理 st,比较处理后的字符串是否相等。

完整代码:

cpp 复制代码
class Solution {
public:
    bool backspaceCompare(string s, string t) {
        return changeStr(s) == changeStr(t);
    }

    string changeStr(string& s)
    {
        string tmp; // 用string模拟栈
        for(auto ch : s)
        {
            if(ch != '#') tmp += ch;
            else 
            {
                if(tmp.size()) tmp.pop_back();
            }
        }
        return tmp;
    }
};

复杂度分析:

  • 时间复杂度:O(n+m)O(n+m)O(n+m),n/m 分别为 s/t 的长度,遍历处理两个字符串。
  • 空间复杂度:O(n+m)O(n+m)O(n+m),存储处理后的两个字符串。

三、基本计算器 II

题目描述:

实现一个基本计算器,计算包含 +、-、*、/ 的字符串表达式的值(表达式不含括号,仅包含非负整数和空格)。

示例

  • 输入:s = "3+2*2",输出:7
  • 输入:s = " 3/2 ",输出:1

解题思路:

用栈处理"加减延迟计算,乘除立即计算"的优先级逻辑:

  1. 初始化栈和运算符 op(默认 '+'),遍历表达式:
    • 跳过空格;
    • 遇到数字,解析完整数值 tmp
    • 根据当前运算符处理数值:
      • +:数值入栈;
      • -:负数值入栈;
      • *//:弹出栈顶数值,与当前数值计算后重新入栈。
  2. 遍历结束后,栈中所有数值求和即为结果。

完整代码:

cpp 复制代码
class Solution {
public:
    int calculate(string s) {
        vector<int> st; // 用vector模拟栈
        int i = 0, n = s.size();
        int op = '+'; // 记录当前运算符,初始为+
        while(i < n)
        {
            if(s[i] == ' ') i++; // 跳过空格
            else if(s[i] >= '0' && s[i] <= '9')
            {
                // 解析完整数字
                int tmp = 0;
                while(i < n && s[i] >= '0' && s[i] <= '9') tmp = tmp * 10 + (s[i++] - '0');
                // 根据运算符处理
                if(op == '+') st.push_back(tmp);
                else if(op == '-') st.push_back(-tmp);
                else if(op == '*') st.back() *= tmp;
                else st.back() /= tmp; // 题目保证除数不为0
            }
            else
                op = s[i++]; // 更新运算符
        }

        // 求和得到结果
        int ret = 0;
        for(auto x : st) ret += x;
        return ret;
    }
};

复杂度分析:

  • 时间复杂度:O(n)O(n)O(n),n 为表达式长度,每个字符仅遍历一次。
  • 空间复杂度:O(n)O(n)O(n),栈存储中间计算结果(最坏情况存储所有加减项)。

四、字符串解码

题目描述:

给定编码字符串(格式如 k[encoded_string],表示将 encoded_string 重复 k 次),返回解码后的字符串。

示例

  • 输入:s = "3[a]2[bc]",输出:"aaabcbc"
  • 输入:s = "3[a2[c]]",输出:"accaccacc"

解题思路:

用两个栈分别存储"重复次数"和"待拼接的字符串",处理嵌套解码:

  1. 初始化数字栈 nums 和字符串栈 st(初始压入空字符串,简化拼接逻辑);
  2. 遍历字符串:
    • 遇到数字:解析完整数字,压入 nums
    • 遇到 '[':解析后续的字母串,压入 st
    • 遇到 ']':弹出栈顶字符串和重复次数,将字符串重复后拼接到新的栈顶;
    • 遇到字母:直接拼接到栈顶字符串。
  3. 遍历结束后,栈顶字符串即为结果。

完整代码:

cpp 复制代码
class Solution {
public:
    string decodeString(string s) {
        stack<int> nums; // 存储重复次数
        stack<string> st; // 存储待拼接的字符串
        st.push(""); // 初始空串,避免栈空判断
        int i = 0, n = s.size();
        while(i < n)
        {
            if(s[i] >= '0' && s[i] <= '9')
            {
                // 解析完整数字
                int tmp = 0;
                while(s[i] >= '0' && s[i] <= '9') 
                    tmp = tmp * 10 + (s[i++] - '0');
                nums.push(tmp);
            }
            else if(s[i] == '[')
            {
                i++;
                // 解析[]内的字母串
                string tmp;
                while(s[i] >= 'a' && s[i] <= 'z')
                    tmp += s[i++];
                st.push(tmp);
            }
            else if(s[i] == ']')
            {
                // 弹出并重复拼接
                string tmp = st.top();
                st.pop();
                int k = nums.top();
                nums.pop();
                while(k--)
                {
                    st.top() += tmp;
                }
                i++;
            }
            else
            {
                // 直接拼接字母
                string tmp;
                while(s[i] >= 'a' && s[i] <= 'z')
                    tmp += s[i++];
                st.top() += tmp;
            }
        }
        return st.top();
    }
};

复杂度分析:

  • 时间复杂度:O(L)O(L)O(L),L 为解码后字符串的总长度(重复拼接的总操作数)。
  • 空间复杂度:O(L)O(L)O(L),栈存储中间字符串和数字。

五、验证栈序列

题目描述:

给定两个整数序列 pushedpopped,判断是否可以通过对 pushed 执行入栈操作,按 popped 的顺序执行出栈操作。

示例

  • 输入:pushed = [1,2,3,4,5], popped = [4,5,3,2,1],输出:true
  • 输入:pushed = [1,2,3,4,5], popped = [4,3,5,1,2],输出:false

解题思路:

模拟栈的入栈/出栈过程:

  1. 遍历 pushed 数组,将元素依次入栈;
  2. 每次入栈后,检查栈顶是否与 popped 的当前元素匹配:
    • 若匹配,弹出栈顶,popped 指针后移;
  3. 遍历结束后,若 popped 指针遍历完所有元素,说明序列合法。

完整代码:

cpp 复制代码
class Solution {
public:
    bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
        stack<int> st;
        int i = 0, n = popped.size();
        for(auto x : pushed)
        {
            st.push(x);
            // 尽可能出栈
            while(st.size() && popped[i] == st.top())
            {
                st.pop();
                i++;
            }
        }
        return i == n; // 所有出栈操作完成则合法
    }
};

复杂度分析:

  • 时间复杂度:O(n)O(n)O(n),n 为序列长度,每个元素最多入栈/出栈一次。
  • 空间复杂度:O(n)O(n)O(n),最坏情况栈存储所有元素。
相关推荐
偷吃的耗子21 小时前
【CNN算法理解】:CNN平移不变性详解:数学原理与实例
人工智能·算法·cnn
dazzle1 天前
机器学习算法原理与实践-入门(三):使用数学方法实现KNN
人工智能·算法·机器学习
那个村的李富贵1 天前
智能炼金术:CANN加速的新材料AI设计系统
人工智能·算法·aigc·cann
张张努力变强1 天前
C++ STL string 类:常用接口 + auto + 范围 for全攻略,字符串操作效率拉满
开发语言·数据结构·c++·算法·stl
万岳科技系统开发1 天前
食堂采购系统源码库存扣减算法与并发控制实现详解
java·前端·数据库·算法
张登杰踩1 天前
MCR ALS 多元曲线分辨算法详解
算法
YuTaoShao1 天前
【LeetCode 每日一题】3634. 使数组平衡的最少移除数目——(解法一)排序+滑动窗口
算法·leetcode·排序算法
波波0071 天前
每日一题:.NET 的 GC是如何分代工作的?
算法·.net·gc
风暴之零1 天前
变点检测算法PELT
算法
深鱼~1 天前
视觉算法性能翻倍:ops-cv经典算子的昇腾适配指南
算法·cann