【ONE·基础算法 || 栈 】

本文详细介绍了编程中涉及的几个栈题型,包括删除字符中的相邻重复项、比较含退格的字符串、基本计算器II、字符串解码和验证栈序列。通过实例和题解,展示了如何利用栈的数据结构特性解决问题。
摘要由CSDN通过智能技术生成

在这里插入图片描述

总言

  主要内容:编程题举例,熟悉理解以栈此类数据结构为主的题型。
  
  


  
  
  
  

1、栈

  栈(Stack)是一种重要的数据结构,具有后进先出(LIFO,Last In First Out)的特性。在算法设计和实现中,栈的应用非常广泛,此类题常见以模拟为主,但关键在于能否想到使用栈来完成。
  
  1、①可以直接使用C++标准库提供了std::stack模板类(实现了栈的功能)。②也可以使用数组或其它结构直接模拟栈的思想特性。
  2、除了下述习题,这里附带其它有关栈的习题链接:
    收录一(有效的括号、用队列实现栈、用栈实现队列、设计循环队列);
    收录二(最小栈、栈的压入与弹出序列、逆波兰表达式求值)
  
  
  

2、删除字符中的所有相邻重复项(easy)

  题源:链接

在这里插入图片描述
  
  

2.1、题解

  1)、思路分析
  说明:根据题意,要判断当前元素是否为重复项,则需要知道其前一个元素的信息,可用「栈」来保存信息,直接取栈顶元素与当前遍历元素判断即可。
  这里有一个优化细节,若直接使用「栈」std::stack来模拟整个过程,结束后还需要从栈中将最终获取到的结果取出。因此,不如直接用「数组」模拟栈特性结构:这里以数组尾部为栈顶,「尾插尾删」即「进栈出栈」。如此一来,数组中存留的内容就是最后的需要返回的结果。
在这里插入图片描述

  
  
  2)、题解
  注意细节:string::back()的使用:

If the string is not empty, the function never throws exceptions (no-throw guarantee).
Otherwise, it causes undefined behavior.
class Solution {
public:
    string removeDuplicates(string s) {
        string ret(s,0,1);//用数组模拟栈,栈堆即数组尾部。
        for(int i = 1; i < s.size(); ++i)//遍历,入栈
        {
            if(!ret.empty() && s[i] == ret.back()) //栈不为空,且栈顶元素与当前待入栈元素相同
                ret.pop_back();// 出栈
            else
                ret.push_back(s[i]);// ⼊栈
        }
        return ret;
    }
};

  简化版写法:实则为各接口的调用。

class Solution {
public:
    string removeDuplicates(string s) {
        string ret; // 搞⼀个数组,模拟栈结构即可
        for (auto ch : s) {
            if (ret.size() && ch == ret.back())//栈中有元素并且栈顶元素和当前遍历到的元素相同
                ret.pop_back(); // 出栈
            else
                ret += ch; // ⼊栈
        }
        return ret;
    }
};

  
  
  
  
  
  
  

3、比较含退格的字符串(easy)

  题源:链接

在这里插入图片描述

  
  

3.1、题解

  1)、思路分析
  需要理解这里退格的含义:实则为退格键(Backspace)的功能操作,使光标左移一格。
  举例:对于给定字符串ab#,当前文本框中输入为ab,遇到#退格,则文本框汇中输入为a
  
  整体思路: 此题可划分为模拟。①先根据给定字符串获得模拟后的文本结果,②再比较两字符串是否相同。
  
  对①模拟过程,由于这里退格符合「后进先出」的特性,因此可以使用「栈」结构来模拟退格的过程。为了方便统计结果,可使用「数组」(这里是字符串string)来模拟实现栈结构

当遇到⾮ # 字符时,直接进栈;
当遇到 # 时,栈顶元素出栈。

  此外,需要注意题目提示:对空文本输入退格字符,文本继续为空。 在模拟中,则表示栈中元素为空时,不能再继续出栈。
  
  
  2)、题解

class Solution {
public:
    string newString(string& s)
    {
        string ret;//模拟栈结构,数组尾为栈顶
        for(auto ch : s)
        {
            if(ch == '#') 
            {
                if(!ret.empty()) //防止连续多个####导致文本为空,此时不能再删除(空文本输入退格字符,文本继续为空:"y#f#o##f")
                    ret.pop_back();
            }
            else ret += ch;
        }
        return ret;
    }
    bool backspaceCompare(string s, string t) {
        //获取输出结果
        string news = newString(s);
        string newt = newString(t);
        //比较
        return news.compare(newt) == 0;//也可以直接比较:return newString(s) == newString(t);
    }
};

  
  
  
  
  
  
  

4、基本计算器 II(medium)

  题源:链接

在这里插入图片描述

  
  

4.1、题解

  1)、思路分析
  注意这里提示内容,这里的基本计算器减少了实际需要处理的工作量。根据题目可以得知以下细节内容:
  1、运算符优先级:给定字符串中只有「加减乘除」四个运算,没有括号等改变优先级。可以知道的是乘除运算在加减之前。
  2、字符串中有空格,非有效字符,需要对其处理。
  3、大数提取:由于是字符串,这里给定的运算数不一定只有个位,如XXX ▢ XX ,因此这里涉及对多位数的字符提取(相关操作之前遇到过:高精度求和)。
  
  此题题解: 利用栈模拟计算过程vector<int> ,先计算乘除法,其结果用栈保存,最后对栈中剩余元素进行求和(加减法),即最终结果。
根据遇到的运算符,分情况讨论即可。

  使用一个变量op来记录遇到的操作符,在遍历时:
  1、若遇到操作符op:更新当前操作符。
  2、若遇到数字,①先将完整的运算数num1提取出来,②根据最新一次保存的操作符op,判断后续操作。
    a、若op == '+',将运算数num1入栈;
    b、若op == '-',将运算数的相反数-num1入栈。
    c、若op == '*',提取栈顶元素num2,与num1进行乘运算,再将运算结果放回栈中。(这里简化操作:可以直接将num1乘到栈顶元素上(使用vector作为栈,back()返回引用值)。)
    d、若op == '/',提取栈顶元素num2,与num1进行除运算,再将运算结果放回栈中。(这里简化操作:可以直接将num1除到栈顶元素上(使用vector作为栈,back()返回引用值)。)

在这里插入图片描述
  
  
  2)、题解

class Solution {
public:
    int calculate(string s) {
        
        char op = '+';//保存最新一次操作符
        vector<int> tmp;//用于模拟栈(保存有历史运算数)
        int i = 0; int len = s.size();
        while(i < len)
        {
            //当前i是否为空格
            if(s[i] == ' ') i++;

            //判断当前位置是操作符还是运算数
            if(!isdigit(s[i]) && s[i] != ' ')
            {   //当前遍历到的是操作符
                op = s[i++];//更新op,遍历下一个位置
            }
            else
            {   //当前遍历到的是操作数
                //1、提取操作数
                int num1 = 0;//当前遍历提取到的运算数
                while(i < len && isdigit(s[i]))
                {
                    num1 = num1*10 + (s[i]-'0');
                    i++;
                }

                //2、根据该数的左侧操作符(op中保存的值),判断是否运算
                if(op == '+') tmp.push_back(num1);
                else if(op == '-') tmp.push_back(-num1);
                else if(op == '*' || op == '/')
                {   //提取栈顶元素做运算
                    int num2 = 0;
                    if(!tmp.empty())//栈中有元素时
                    {
                        num2 = tmp.back();
                        tmp.pop_back();//删除栈顶元素
                    }
                    //将运算结果入栈
                    if(op == '*') tmp.push_back(num1*num2);
                    else if(op == '/') tmp.push_back(num2 / num1);//注意除法这里的运算顺序
                }
            }
        }

        //将栈中元素累加
        int ret = 0;
        for(auto num : tmp)
            ret += num;
        
        //返回最终运算结果
        return ret;
    }
};

  vector::back()返回引用,可直接对值修改。

reference back();
const_reference back() const;
                //2、根据该数的左侧操作符(op中保存的值),判断是否运算
                if(op == '+') tmp.push_back(num1);
                else if(op == '-') tmp.push_back(-num1);
                else if(op == '*') tmp.back() *= num1;//reference back();可直接修改值
                else if(op == '/') tmp.back() /= num1;//注意除法这里的运算顺序

  
  
  
  
  
  

5、字符串解码(medium)

  题源:链接

在这里插入图片描述
  
  

5.1、题解

  1)、思路分析
  同上一题,使用栈模拟,分情况讨论。
  解析此题,相对复杂的是嵌套情况:2[a3[b]],需要先解码内层的3[b]获得bbb,才能对外层2[abbb]进行解码。 因此,我们使用双栈来模拟,一个用于存放数字k(重复出现的次数),一个用于存放该数字对应的字符(重复k次的字符串)。分情况讨论如下:
  1、遇到数字:提取出这个数字,放入”数字栈"中;
  2、遇到'[':把后面的字符串提取出来,放入“字符串栈”中;
  3、遇到']':可以进行一次解析,将结果放回"字符串栈"栈顶的字符串后面;
  4、遇到单独的字符:提取出来这个字符串,直接放在"字符串栈"栈顶的字符串后面

  细节:字符串这个栈中,先放入一个空串。
  
  2)、题解

class Solution {
public:
    string decodeString(string s) {
        //双栈
        stack<int> nums;//存放数字的栈
        stack<string> strs;//存放字符串的栈
        strs.push("");

        int i = 0; int len = s.size();
        //遍历,模拟过程,分情况讨论
        while(i < len)
        {
            if(isdigit(s[i]))//若当前遍历到的是数字
            {
                //因数组可能不止一位,需要提取数字,再放入数字栈中
                int k = 0;
                while(i < len && isdigit(s[i]))
                    k = k*10 + s[i++] -'0';
                nums.push(k);
            }
            else if(s[i] == '[')//若当前遍历到的是左括号
            {   
                
                ++i;//跳过该'[',提取'['之后的字符串,放入字符栈中
                string tmp;
                while(i < len && isalpha(s[i]))
                    tmp += s[i++];
                strs.push(tmp);
            }
            else if(s[i] == ']')//若遇到右括号,可进行一次解码操作
            {
                //分别提取数字栈和字符栈的栈顶元素
                int k = nums.top(); 
                nums.pop();

                string str = strs.top();
                strs.pop();

                //解码,存放入字符栈中;
                string tmp;
                while(k--) tmp += str;
                strs.top() += tmp;//value_type& top();

                ++i;//当前情况处理结束,可以跳过该']',继续后续的遍历

            }
            else if(isalpha(s[i]))//若遇到的是字符
            {
                //提取字符,将其放在栈顶元素之后。
                string tmp;
                while(i < len && isalpha(s[i]))
                    tmp += s[i++];
                strs.top() += tmp;
            }

        }
        return strs.top();
    }
};

  
  
  
  
  

6、验证栈序列(medium)

  题源:链接

在这里插入图片描述

  
  

6.1、题解

  1)、思路分析
  用栈来模拟进出栈的流程: 一直让元素进栈,进栈的同时判断是否需要出栈。当所有元素模拟完毕之后,如果栈中还有元素,那么就是一个非法的序列。否则,就是一个合法的序列。
在这里插入图片描述

  
  2)、题解

class Solution {
public:
    bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
        stack<int> st;
        int i = 0;//用于指向当前poped中待出栈元素
        for(auto x : pushed)
        {
            st.push(x);//让元素入栈
            //判断当前入栈元素是否是出栈元素
            while(!st.empty() && st.top() == popped[i])
            {
                st.pop();
                i++;
            }
        }
        return st.empty();
    }
};

  
  
  
  
  
  
  
  

Fin、共勉。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值