• 【leetcode 力扣刷题】栈和队列的基础知识 + 栈的经典应用—匹配


    栈和队列基础知识

    数据结构课程介绍线性结构的时候,介绍有线性表、链表、栈和队列。线性表,比如array、vector可以直接用下标定位到相应元素,但是删除元素时,需要移动其他元素,不能原地删除;链表不能用下标定位,是通过指针来定位到相应元素的地址空间,但添加or删除元素时,时间复杂度是O(1)的;栈和队列是比较特殊的线性结构,特殊在于栈和队列中的元素不能随意的访问——对于栈,只能访问栈顶元素(用top()方法),只能向栈顶添加元素(用push()方法),只能从栈顶取元素(用pop()方法)。我们可以把栈看作是一个瓶子,依次从瓶口(即栈顶)扔进去乒乓球,倒出来的时候不能从瓶底拿,也只能从瓶口,而且拿出来的顺序跟放进去的是反的,即先进后出,后进先出;对于队列,则有两个开口,可以看作有两个口的管道,从一个方向把球塞进去(push()),从另外一个方向取出来(pop()),那么取出来的顺序还是和塞进去的顺序一样的,即先进先出,后进后出
    在这里插入图片描述
    C++ STL中stack和queue实际上并不是容器,而是容器适配器。比如声明一个stack变量的语句是stack> ,如果不说明是用什么容器,就默认用deque,如果说明了用vector,用的就是vector——stack > mystk
    C++STL中stack的成员函数及作用如下表:

    成员函数作用
    size()返回堆栈中元素的数量
    empty()检查堆栈是否为空。如果堆栈为空,则返回true;否则,返回false
    top()返回堆栈顶部元素的引用,但不会删除该元素;如果堆栈为空,则行为是未定义的
    pop()从堆栈的顶部移除元素,但不返回其值;如果堆栈为空,则行为是未定义的
    push(const T& value)将元素插入到堆栈的顶部;参数value是要插入的元素的值
    emplace(Args&&... args)通过在堆栈的顶部构造一个新的元素来插入元素;参数args是用于构造元素的参数

    这里注意push()和emplace()两个成员函数都能实现在向栈顶压入一个元素,但是push要先创建value的副本,而emplace是直接插入元素。总之emplace效率更高。……但我不是很懂……
    queue的成员函数及作用如下表:

    成员函数作用
    empty()检查队列是否为空。如果队列为空,则返回true;否则,返回false
    size()返回队列中元素的数量
    front()返回队列头部的元素的引用,但不会删除该元素;如果队列为空,则行为是未定义的
    back()返回队列尾部的元素的引用,但不会删除该元素;如果队列为空,则行为是未定义的
    pop()从队列的头部移除元素,但不返回其值;如果队列为空,则行为是未定义的
    push(const T& value)将元素插入到队列的尾部;参数value是要插入的元素的值
    emplace(Args&&... args)通过在队列的尾部构造一个新的元素来插入元素;参数args是用于构造元素的参数

    232. 用栈实现队列

    题目链接:232. 用栈实现队列
    题目内容:
    在这里插入图片描述
    思路:栈是先入后出的,push和pop都只能在栈顶;但是队列是对头方向pop,队尾方向实现push。根据这俩的特性,可以知道对于push操作是没有差异的,直接末尾push就好。对于pop,先入栈的元素在栈的底端,如何才能先出栈?——因为题目中说了可以用两个栈,那么一个栈A用作入队,一个栈B用作出队。压入栈A中的元素,再依次取出并压入栈B,栈B的出栈顺序就和队列的出队顺序一样了。比如下图,依次入队是(出队也是)1,2,3,4,先放入栈A,再将栈A的元素依次取出并放入栈B,此时栈B内元素的出栈顺序也是1,2,3,4了。
    在这里插入图片描述
    因此出队pop()操作就是将栈A的元素全部依次取出并放入栈B中。 但是当栈B不为空的时候呢? 这里需要意识到栈B中所有的元素都是在栈A之前的,如果栈B不为空,就直接pop栈顶元素,即为队头元素;如果栈B为空,再从栈A中取出元素放入栈B。
    代码如下(C++):

    class MyQueue {
    private:
    	//两个栈,一个用于出队,一个用于入队
        stack<int> InStack;
        stack<int> OutStack;
    public:
        MyQueue() {
        }
        //push直接在入队栈的栈顶push就好    
        void push(int x) {
            InStack.push(x);
        }
        int pop() {
        	//如果出堆栈为空,先将入队栈的元素依次取出并push进出队栈
            if(OutStack.empty()){
                while(!InStack.empty()){
                    OutStack.push(InStack.top());
                    InStack.pop();
                }
            }
            //直接从出队栈栈顶pop,即为队头元素
            int result = OutStack.top();
            OutStack.pop();
            return result;
        }
        //peek是返回队头元素但是不删除,可以复用pop的代码,只是需要push回去
        int peek() {
            int result = this->pop();
            OutStack.push(result);
            return result;
        }
        //入队栈和出队栈都为空才为空
        bool empty() {
            if(InStack.empty() && OutStack.empty())
                return true;
            return false;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    225. 用队列实现栈

    题目链接:225. 用队列实现栈
    题目内容:
    在这里插入图片描述
    前面用栈实现队列里说到,push的时候直接push就行,现在用队列实现栈也是,push的时候直接在队列的末尾push就好。题目说用两个队列,实际上用一个队列即可。用栈实现队列的时候两个栈各有作用,一个入队栈一个出队栈,将一个栈的元素依次取出再push到另外一个栈能够实现出栈顺序的颠倒。但是对于队列,一个队列的元素依次取出再放入另外一个队列,实际还是一样的顺序,而这个操作可以通过将队头元素pop()后直接push到队尾来实现,因此只需要一个队列即可。
    所以,对于栈的pop,需要找到队列的末尾元素,从队头方向,依次取出元素再push到末尾,直到之前的队尾元素现在在队头了【循环size-1次】,直接pop。
    代码如下(C++):

    class MyStack {
    private:
    	//用于实现栈的队列,只需要一个
        queue<int> que1;
    public:
        MyStack() {
        }
        //直接队列末尾push即可
        void push(int x) {
            que1.push(x);
        }
        //将队头元素pop再push到队尾,循环size-1次,栈顶的元素就到了队头了
        int pop() {
            int size = que1.size() - 1;
            int top;
            //循环size-1次
            while(size){
               top = que1.front();
               que1.pop();
               que1.push(top);
               size--;
            }
            //队头元素
            top = que1.front();
            que1.pop();
            return top;        
        }
        //栈顶即队尾元素,queue有直接取队尾元素的成员函数
        int top() {
            return que1.back();
        }
        //队列不为空,栈就不为空;队列空了栈也空了    
        bool empty() {
            return que1.empty();
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    20. 有效的括号

    题目链接:20. 有效的括号
    题目内容:
    在这里插入图片描述
    有效括号可以概括为两点:

    • 每个左括号都有一个对应的类型的右括号闭合;每个右括号都能与一个对应类型的左括号闭合;即每种类型的左括号和右括号数量要一样,有2个"(“,就得有2个”)";
    • 左括号必须以正确的顺序闭合,比如"( [ ) ]“就是不正确的,应该让”["先闭合;
      基于以上两个特点,我们可以用栈来完成这个题目。是左括号"(" “[” "{"就入栈;是右括号就判断后栈顶的左括号是否匹配【保证左括号以正确的顺序闭合——内层的括号先闭合】。

    如果最终多了右括号或者左括号就说明是不匹配的。
    代码实现(C++):

    class Solution {
    public:
        bool isValid(string s) {
        	//括号数量是奇数一定不能正确匹配
            if(s.size() & 1)
                return false;
            stack<char> buff; //存左括号
            for(int i = 0; i < s.size(); i++){
                //是左括号,就直接入栈
                if(s[i] == '(' || s[i] == '[' || s[i] == '{')
                    buff.push(s[i]);
                //是右括号,并且左括号栈不为空
                else if(!buff.empty()){
                	//如果和栈顶括号匹配
                    if(s[i] == ')' && buff.top() == '('
                     ||s[i] == ']' && buff.top() == '['
                     ||s[i] == '}' && buff.top() == '{') 
                        buff.pop(); //栈顶左括号出栈,表示已经匹配了
                    else 
                    //和栈顶左括号不匹配直接返回false
                        return false;
                    }
                else //是右括号,但是左括号栈为空,直接返回false
                    return false;
            }
        //右括号匹配完了,如果栈内还要左括号返回false,如果左括号栈也为空,返回true
        return buff.empty() ? true : false;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    可以用map来存左右括号的对应关系,简化代码:

    class Solution {
    public:
        bool isValid(string s) {
            if(s.size() & 1)
                 return false;
            unordered_map <char,char> pairs;
            //左右括号对应关系
            pairs['('] = ')';
            pairs['['] = ']';
            pairs['{'] = '}';
    		//存储左括号
            stack<char> zuo;
            for(char ch : s){
            	//左括号直接入栈
                if(pairs.count(ch))
                    zuo.push(ch);
                //右括号
                else{
                	//左括号为空,或者栈顶左括号和当前右括号不匹配
                    if(zuo.empty() || ch != pairs[zuo.top()])
                        return false;
                    //相反情况就是匹配,直接栈顶左括号出栈
                    zuo.pop();
                }
            }
        return zuo.empty();
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    1047. 删除字符串中的所有相邻重复项

    题目链接:1047. 删除字符串中的所有相邻重复项
    题目内容:
    在这里插入图片描述
    根据给出的例子可以看出,abbaca删除bb后,又会出现aa可以删除,因此还是用栈这个结构来实现:

    • 遍历字符串,如果当前字符和栈顶元素不同直接入栈;
    • 如果相同,该字符不保存,同时栈顶的相同元素也要删除。

    这里注意,可以用string来实现,利用其pop_back()和push_back()这个类似stack操作的成员函数,在string末尾添加、删除元素。代码实现(C++):

    
    class Solution {
    public:
        string removeDuplicates(string s) {
            string ans;
            for(char ch : s){
            	//如果和字符串末尾【即栈顶】元素相同,栈顶元素删除
                if(!ans.empty() && ans.back() == ch)
                    ans.pop_back();
                //不相同就直接保存
                else 
                    ans.push_back(ch);
            }
            return ans;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
  • 相关阅读:
    当新手小白有了一块【香橙派OrangePi AIpro】.Demo
    最长的可整合子数组的长度
    300PLCmpi转以太网通过CHNet-S7300与LABVIEW OPC通信
    stack使用+模拟实现
    怎么维护自己的电脑?
    常见面试题-MySQL软删除以及索引结构
    全文解读 | 五部委印发:元宇宙产业创新发展三年行动计划(2023-2025年)
    电子科技大学 分布式系统 期末复习笔记
    day04_运算符
    Ubuntu docker安装mysql
  • 原文地址:https://blog.csdn.net/massive_jiang/article/details/132862272