【C++代码】有效的括号,删除字符串中的所有相邻重复项,逆波兰表达式求值,栈的压入、弹出序列--代码随想录

题目:有效的括号

  • 给定一个只包括 '('')''{''}''['']' 的字符串 s ,判断字符串是否有效。有效字符串需满足:

    • 左括号必须用相同类型的右括号闭合。

    • 左括号必须以正确的顺序闭合。

    • 每个右括号都有一个对应的相同类型的左括号。

题解
  • 判断括号的有效性可以使用「栈」这一数据结构来解决。我们遍历给定的字符串 s。当我们遇到一个左括号时,我们会期望在后续的遍历中,有一个相同类型的右括号将其闭合。由于后遇到的左括号要先闭合,因此我们可以将这个左括号放入栈顶。

  • 当我们遇到一个右括号时,我们需要将一个相同类型的左括号闭合。此时,我们可以取出栈顶的左括号并判断它们是否是相同类型的括号。如果不是相同的类型,或者栈中并没有左括号,那么字符串 s 无效,返回 False。为了快速判断括号的类型,我们可以使用哈希表存储每一种括号。哈希表的键为右括号,值为相同类型的左括号。

  • 在遍历结束后,如果栈中没有左括号,说明我们将字符串 s 中的所有左括号闭合,返回 True,否则返回 False。注意到有效字符串的长度一定为偶数,因此如果字符串的长度为奇数,我们可以直接返回 False,省去后续的遍历判断过程。

  • class Solution {
          
          
    public:
        bool isValid(string s) {
          
          
            int len_s = s.size();
            if(len_s%2==1){
          
          
                return false;
            }
            stack<char> char_stack;
            unordered_map<char,char> pairs={
          
          
                {
          
          ')','('},
                {
          
          ']','['},
                {
          
          '}','{'}
            };
            for(int i=0;i<len_s;i++){
          
          
                if(pairs.find(s[i])!=pairs.end()){
          
          
                    if(char_stack.empty() || char_stack.top()!=pairs[s[i]]){
          
          
                        return false;
                    }
                    char_stack.pop();
                }else{
          
          
                    char_stack.push(s[i]);
                }
            } 
            return char_stack.empty();
        }
    };
    
  • 题意其实就像我们在写代码的过程中,要求括号的顺序是一样的,有左括号,相应的位置必须要有右括号。如果还记得编译原理的话,编译器在 词法分析的过程中处理括号、花括号等这个符号的逻辑,也是使用了这种数据结构。再举个例子,linux系统中,cd这个进入目录的命令我们应该再熟悉不过了。这个命令最后进入a目录,系统是如何知道进入了a目录呢 ,这就是栈的应用。

  • 由于栈结构的特殊性,非常适合做对称匹配类的题目。首先要弄清楚,字符串里的括号不匹配有几种情况。建议在写代码之前要分析好有哪几种不匹配的情况,如果不在动手之前分析好,写出的代码也会有很多问题。先来分析一下 这里有三种不匹配的情况,

    • 第一种情况,字符串里左方向的括号多余了 ,所以不匹配。

    • 第二种情况,括号没有多余,但是 括号的类型没有匹配上。

      扫描二维码关注公众号,回复: 16675108 查看本文章
    • 第三种情况,字符串里右方向的括号多余了,所以不匹配。

  • 对应三种情况的解法:

    • 第一种情况:已经遍历完了字符串,但是栈不为空,说明有相应的左括号没有右括号来匹配,所以return false

    • 第二种情况:遍历字符串匹配的过程中,发现栈里没有要匹配的字符。所以return false

    • 第三种情况:遍历字符串匹配的过程中,栈已经为空了,没有匹配的字符了,说明右括号没有找到对应的左括号return false

  • 那么什么时候说明左括号和右括号全都匹配了呢,就是字符串遍历完之后,栈是空的,就说明全都匹配了。还有一些技巧,在匹配左括号的时候,右括号先入栈,就只需要比较当前元素和栈顶相不相等就可以了,比左括号先入栈代码实现要简单的多了!

    • class Solution {
              
              
      public:
          bool isValid(string s) {
              
              
              int len_s = s.size();
              if(len_s%2==1){
              
              
                  return false;
              }
              stack<char> char_stack;
              // unordered_map<char,char> pairs={
              
              
              //     {')','('},
              //     {']','['},
              //     {'}','{'}
              // };
              // for(int i=0;i<len_s;i++){
              
              
              //     if(pairs.find(s[i])!=pairs.end()){
              
              
              //         if(char_stack.empty() || char_stack.top()!=pairs[s[i]]){
              
              
              //             return false;
              //         }
              //         char_stack.pop();
              //     }else{
              
              
              //         char_stack.push(s[i]);
              //     }
              // } 
              // return char_stack.empty();
              for(int i=0;i<len_s;i++){
              
              
                  if(s[i]=='('){
              
              
                      char_stack.push(')');
                  }else if(s[i]=='{'){
              
              
                      char_stack.push('}');
                  }else if(s[i]=='['){
              
              
                      char_stack.push(']');
                  }else if(char_stack.empty() || char_stack.top()!=s[i]){
              
              
                      return false;
                  }else{
              
              
                      char_stack.pop();
                  }
              }
              return char_stack.empty();
          }
      };
      

题目:删除字符串中的所有相邻重复项

  • 给出由小写字母组成的字符串 S重复项删除操作会选择两个相邻且相同的字母,并删除它们。在 S 上反复执行重复项删除操作,直到无法继续删除。在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。
题解
  • 充分理解题意后,我们可以发现,当字符串中同时有多组相邻重复项时,我们无论是先删除哪一个,都不会影响最终的结果。因此我们可以从左向右顺次处理该字符串。

  • 而消除一对相邻重复项可能会导致新的相邻重复项出现,如从字符串 abba 中删除 bb 会导致出现新的相邻重复项 aa 出现。因此我们需要保存当前还未被删除的字符。一种显而易见的数据结构呼之欲出:栈。我们只需要遍历该字符串,如果当前字符和栈顶字符相同,我们就贪心地将其消去,否则就将其入栈即可。

  • C++ 代码中,由于 std::string 类本身就提供了类似「入栈」和「出栈」的接口,因此我们直接将需要被返回的字符串作为栈即可。对于其他的语言,如果字符串类没有提供相应的接口,则需要在遍历完成字符串后,使用栈中的字符显式地构造出需要被返回的字符串。

  • class Solution {
          
          
    public:
        string removeDuplicates(string s) {
          
          
            string char_stack;
            for(int index=0;index<s.size();index++){
          
          
                if(!char_stack.empty()&&s[index]==char_stack.back()){
          
          
                    char_stack.pop_back();
                }else{
          
          
                    char_stack.push_back(s[index]);
                }
            }
            return char_stack;
        }
    };
    
  • 时间复杂度:O(n),其中 n 是字符串的长度。我们只需要遍历该字符串一次。空间复杂度:O(n) 或 O(1),取决于使用的语言提供的字符串类是否提供了类似「入栈」和「出栈」的接口。注意返回值不计入空间复杂度。

  • 我们在删除相邻重复项的时候,其实就是要知道当前遍历的这个元素,我们在前一位是不是遍历过一样数值的元素,那么如何记录前面遍历过的元素呢?所以就是用栈来存放,那么栈的目的,就是存放遍历过的元素,当遍历当前的这个元素的时候,去栈里看一下我们是不是遍历过相同数值的相邻元素。然后再去做对应的消除操作。 如动画所示:

    • 在这里插入图片描述

    • 从栈中弹出剩余元素,此时是字符串ac,因为从栈里弹出的元素是倒序的,所以再对字符串进行反转一下,就得到了最终的结果。

    • class Solution {
              
              
      public:
          string removeDuplicates(string s) {
              
              
              stack<char> temp_stack;
              for(char ch:s){
              
              
                  if(temp_stack.empty() || temp_stack.top()!=ch){
              
              
                      temp_stack.push(ch);
                  }else{
              
              
                      temp_stack.pop();
                  }
              }
              string res="";
              while(!temp_stack.empty()){
              
              
                  res=res+temp_stack.top();
                  temp_stack.pop();
              }
              reverse(res.begin(),res.end());
              return res;
          }
      };//超出内存限制
      

题目:逆波兰表达式求值

  • 给你一个字符串数组 tokens ,表示一个根据 逆波兰表示法(后缀表达式) 表示的算术表达式。请你计算该表达式。返回一个表示表达式值的整数。

  • 有效的算符为 '+''-''*''/' 。每个操作数(运算对象)都可以是一个整数或者另一个表达式。两个整数之间的除法总是 向零截断 。表达式中不含除零运算。输入是一个根据逆波兰表示法表示的算术表达式。答案及所有中间计算结果可以用 32 位 整数表示。

题解
  • 逆波兰表达式严格遵循「从左到右」的运算。计算逆波兰表达式的值时,使用一个栈存储操作数,从左到右遍历逆波兰表达式,进行如下操作:

    • 如果遇到操作数,则将操作数入栈;如果遇到运算符,则将两个操作数出栈,其中先出栈的是右操作数,后出栈的是左操作数,使用运算符对两个操作数进行运算,将运算得到的新操作数入栈。整个逆波兰表达式遍历完毕之后,栈内只有一个元素,该元素即为逆波兰表达式的值
  • class Solution {
          
          
    public:
        int evalRPN(vector<string>& tokens) {
          
          
            stack<int> str_stack;
            for(int i=0;i<tokens.size();i++){
          
          
                string &tok = tokens[i];
                if(isNumber(tok)){
          
          
                    str_stack.push(atoi(tok.c_str()));
                }else{
          
          
                    int num2 = str_stack.top();
                    str_stack.pop();
                    int num1 = str_stack.top();
                    str_stack.pop();
                    switch(tok[0]){
          
          
                        case '+':
                            str_stack.push(num1+num2);
                            break;
                        case '-':
                            str_stack.push(num1-num2);
                            break;
                        case '*':
                            str_stack.push(num1*num2);
                            break;
                        case '/':
                            str_stack.push(num1/num2);
                            break;
                    }
                }
            }
            return str_stack.top();
        }
        bool isNumber(string &tok){
          
          
            if(tok=="+" || tok=="-" || tok=="*" || tok=="/")
                return false;
            return true;
        }
    };
    
  • 时间复杂度:O(n),其中 n 是数组 tokens 的长度。需要遍历数组 tokens 一次,计算逆波兰表达式的值。空间复杂度:O(n),其中 n 是数组 tokens 的长度。使用栈存储计算过程中的数,栈内元素个数不会超过逆波兰表达式的长度。

  • 递归就是用栈来实现的。所以栈与递归之间在某种程度上是可以转换的!那么来看一下本题,其实逆波兰表达式相当于是二叉树中的后序遍历。 大家可以把运算符作为中间节点,按照后序遍历的规则画出一个二叉树。但我们没有必要从二叉树的角度去解决这个问题,只要知道逆波兰表达式是用后序遍历的方式把二叉树序列化了,就可以了

  • 本题中每一个子表达式要得出一个结果,然后拿这个结果再进行运算,那么这岂不就是一个相邻字符串消除的过程

    • 在这里插入图片描述
  • 习惯看到的表达式都是中缀表达式,因为符合我们的习惯,但是中缀表达式对于计算机来说就不是很友好了。

题目: 栈的压入、弹出序列

  • 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列 {1,2,3,4,5} 是某栈的压栈序列,序列 {4,5,3,2,1} 是该压栈序列对应的一个弹出序列,但 {4,3,5,1,2} 就不可能是该压栈序列的弹出序列。
题解
  • 尝试按照 popped 中的顺序模拟一下出栈操作,如果符合则返回 true,否则返回 false。这里用到的贪心法则是如果栈顶元素等于 popped 序列中下一个要 pop 的值,则应立刻将该值 pop 出来。

  • 使用一个栈 st 来模拟该操作。将 pushed 数组中的每个数依次入栈,同时判断这个数是不是 popped 数组中下一个要 pop 的值,如果是就把它 pop 出来。最后检查栈是否为空。

  • class Solution {
          
          
    public:
        bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
          
          
            stack<int> temp_stack;
            int n=popped.size();
            int j=0;
            for(int i=0;i<pushed.size();i++){
          
          
                temp_stack.push(pushed[i]);
                while(!temp_stack.empty() && j<n && temp_stack.top()==popped[j]){
          
          
                    temp_stack.pop();
                    j++;
                }
            }
            return temp_stack.empty();
        }
    };
    
  • 时间复杂度:O(N)。将所以元素一遍入栈,一遍出栈,需要 O(2N)。空间复杂度:O(N)。使用了辅助栈 temp_stack

猜你喜欢

转载自blog.csdn.net/weixin_43424450/article/details/132677963