【栈和队列】数据结构与算法——代码随想录

目录

  • (一)栈和队列的基础知识
    • 1.C++中的stack是容器吗?
    • 2.应用
  • (二)栈和队列的通用操作
    • 1.栈的基本操作
    • 2.队列的基本操作
  • (三)栈和队列的相关例题
    • 1.用栈实现队列
    • 2.用队列实现栈
    • 3.有效的括号
    • 4.删除字符串中所有与相邻重复项
    • 5.逆波兰表达式求值
    • 6.滑动窗口最大值
    • 7.前K个高频元素

参考资料
代码随想录

(一)栈和队列的基础知识

1.C++中的stack是容器吗?

a.C++中容器有哪些特点
b.栈有哪些特点
c.总结栈为什么不是容器
STL库是C++中借助模板把常用的数据结构和算法分离的库
基本组件除了容器还有迭代器算法和函数对象,算法运行时通过迭代器对容器进行访问;

简单的理解容器,它就是一些模板类的集合,但和普通模板类不同的是,容器中封装的是组织数据的方法(也就是数据结构)。

栈本身不归类为容器,但是栈是以底层容器来完成所有功能的。栈的底层实现可以是vector、deque、list,栈归类为容器适配器。

栈提供push和pop等接口,所有元素必须符合先进后出的规则,不提供走访功能,也不提供迭代器。不像set或map提供迭代器iterator来遍历所有元素。

2.应用

匹配类型的题目
【栈和队列】数据结构与算法——代码随想录_第1张图片

(二)栈和队列的通用操作

1.栈的基本操作

push(x)——将一个元素放入栈的顶部
pop()——从栈顶弹出元素
peek()——返回栈最前端的元素
empty()——返回栈是否为空

2.队列的基本操作

push(x)——将一个元素放入队列的尾部
pop()——从队列尾部移除元素
peek()——返回队列首部的元素
empty()——返回队列是否为空

(三)栈和队列的相关例题

1.用栈实现队列

用栈实现队列
(1)基本原理
栈是后进先出,队列是先进先出;
相关操作:

操作名称 操作解释
push(x) 将一个元素放入队列的尾部
pop() 从队列首部移除元素
peek() 返回队列首部的元素
empty() 返回队列是否为空
(2)代码实现
步骤:
a.定义两个栈来表示队列
b.将元素压入栈等同于压入队列
c.元素出栈,需要两个栈配合,stOut为空时,再从stIn导入数据
d.返回首部的元素,读取之后还需要放回去
e.两个栈都为空时,才能判断队列为空
class MyQueue {
public:
    stack<int> stIn;  //定两个栈来实现队列
    stack<int> stOut;
    MyQueue() {

    }
    
    void push(int x) {
        stIn.push(x);//直接放入栈,等同于直接放入队列的尾部

    }
    
    int pop() {
        //先把stIn中的数据导入stOut,再从stOut中弹出数据,相当于做了一下顺序的调换
        if(stOut.empty()){
            while(!stIn.empty()){
                stOut.push(stIn.top());//top是读取顶端元素,pop是弹出元素
                stIn.pop();
            }
        }
        int result = stOut.top();
        stOut.pop();
        return result;

    }
    
    int peek() {
        int res = this->pop();//使用pop读取开头元素,定义了之前已经有的函数
        stOut.push(res);//再放回去
        return res;

    }
    
    bool empty() {
        return stIn.empty()&&stOut.empty();
    }
};

2.用队列实现栈

用队列实现栈
(1)基本原理
用两个队列来模拟栈,队列先进先出,栈先进后出,把队列后出的数据放入另一个队列先进行缓存。
方法一:两个队列,重点在出栈时会将最后一个元素前面的所有值存放到另一个队列
方法二:只用一个队列也能达到效果,将队列弹出的元素不断添加到队列的末尾
(2)代码实现
步骤
a.定义两个队列
b.将元素压入队列等同于压入栈
c.元素出栈,需要两个队列配合,依次将que1除了最后一个元素以外放入que2;
首先记录size,然后用size–作为循环条件;
返回元素que1front元素
d.栈的top就是队列的末尾
e.两个栈都为空时,才能判断队列为空

class MyStack {
public:
    queue<int> que1;
    queue<int> que2;
    MyStack() {

    }
    
    void push(int x) {
        que1.push(x);

    }
    
    int pop() {
        //为了记录最后一个元素需要知道队列的长度
        int size = que1.size();
        while(--size){//需要留下最后一个元素,所以用--size
//将最后一个元素之前的元素依次放入队列2          
            que2.push(que1.front());
            que1.pop();
        }
        int result = que1.front();
        que1.pop();
        //为了方便处理,所以一直用que1作为需要处理的对象
        que1 = que2;
        while(!que2.empty()){
            que2.pop();
        }
        return result;
    }
    
    int top() {
        return que1.back();//que的基本操作和stack的有所不同,主要区分
    }
    
    bool empty() {
        return que1.empty();

    }
};

3.有效的括号

有效的括号
(1)基本原理
栈数据结构的应用:括号的匹配,匹配类型是数据非常适合用栈来解决
有左括号,相应的位置就必须有右括号;

不匹配情况 解决措施
缺少右括号 遍历字符串匹配,发现栈不为空。
左右括号类型不匹配 遍历字符串,栈中没有相匹配的字符
缺少左括号 遍历没有结束,栈中已经空了
(2)代码实现
步骤:
a.定义一个栈存储一侧的括号类型
b.遇到左括号,存储对应的右括号
(为了方便配对弹出)
c.如果遍历完字符串,栈不为空,则return false;
栈为空则return ture
class Solution {
public:
    bool isValid(string s) {
        stack<int> st;
        for(int i = 0;i<s.size();i++){
            if(s[i]=='('){
                st.push(')');
            }else if(s[i]=='{'){
                st.push('}');
            }else if(s[i]=='['){
                st.push(']');
            }//左侧括号遍历结束以后可以进行判断
            //栈提前空了,说明缺少相应的左括号
            //匹配过程中发现不配对元素
            else if(st.empty()||st.top()!=s[i])//关键步骤
            {return false;}
            else st.pop();//左括号右括号相等就弹出元素
        }
        return st.empty();
    }
};

4.删除字符串中所有与相邻重复项

删除字符串中所有与相邻重复项
(1)基本原理
应用:消消乐
函数的递归调用:每一次递归调用都会把函数的局部变量、参数值返回地址等压入调用栈中。
把字符串顺序放到一个栈中,相同的话栈就弹出,最后栈中剩下的元素都是相邻不相同的元素。
(2)代码实现
a.定义一个栈用来存储字符串中的元素
b.新元素与栈顶元素相同则弹出该元素
c.遍历到数组末尾,一次读取栈中的元素,并逆序输出

class Solution {
public:
    string removeDuplicates(string s) {
        stack<char> st;
        int size = s.size();
        string result = "";//注意定义的时候不要有空格,之前的错误就是因为这里有空格
        for (char a : s) { //这里用数组定义时的for(int i = 1;i
            if (st.empty() || a != st.top()) {
                st.push(a);
            } else {
                st.pop(); // s 与 st.top()相等的情况
            }
        }
        while(!st.empty()){//栈的基本操作中并没有读取他长度的函数
            result+=st.top();//pop是弹出操作,返回的是少一个元素的st.top才是读取最顶层元素的方法
            st.pop();
        }
        reverse(result.begin(),result.end());//reverse()函数的使用,字符串也是有迭代器的
        return result;
    }
};

5.逆波兰表达式求值

逆波兰表达式求值
(1)基本原理
逆波兰表达式:是一种后缀表达式,所谓后缀就是指算符写在后面。
优点:去掉括号后表达式无歧义
知识点之间的联系:栈与递归相互之间可以转换,逆波兰表达式相当于二叉树的后序遍历
(2)代码实现
适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中。
步骤:
a.定义一个栈存储数组中的操作符号和元素
b.先对是否是运算符进行判断
(因为这里的数据不是单个字符,不能用单个数值进行判读)
c.是元素符则弹出两个元素并进行计算
d.是数据则压入栈中
e.将结果弹出

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> stk;
        for(int i = 0;i<tokens.size();i++){
            if(tokens[i]=="+"||tokens[i]=="-"||tokens[i]=="*"||tokens[i]=="/"){
                int tmp1 = stk.top();
                stk.pop();
                int tmp2 = stk.top();
                stk.pop();
                if(tokens[i]=="+") stk.push(tmp1+tmp2);
                if(tokens[i]=="-") stk.push(tmp2-tmp1);
                if(tokens[i]=="*") stk.push(tmp1*tmp2);
                if(tokens[i]=="/") stk.push(tmp2/tmp1);
            }else{
                //因为这一步已经进行了字母到数字的转换,后面的计算可用
                stk.push(stoi(tokens[i]));
            }
        }
        return stk.top();
    }
};

6.滑动窗口最大值

滑动窗口最大值
(1)基本原理
单调队列的经典问题
方法一:遍历一遍,每次从串口中找到最大的数值;
复杂度过高。
思考的关键点在:队列不需要维护窗口中所有的元素,只需要维护窗口中有可能成为最大值的元素。
(2)代码实现
步骤:
a.设计一个单调队列,比较当前数值是否等于队列出口元素的数值
设计队列主要有3个功能,
pop()移除窗口左端的元素,
push()加入窗口右端的元素,
front()弹出队列中的最大值。
b.把第一个窗口里的数值放入队列
c.遍历数组后面的元素,每移动一步执行队列中的各种操作

class Solution {
private:
	//类的定义和使用,类的private函数和public关键字;类的末尾处要有分号。
    class MyQueue{
        public:
            deque<int> que;
            //设计队列的pop功能函数,只有当需要弹出的数值是队列头中时才需要弹出,因为其他地方的数值并没有放入队列
            void pop(int value){
            //双端队列有pop_front()和pop_back()
                if(!que.empty()&&value == que.front()){
                    que.pop_front();
                }
            }
            //如果push的元素大于入口元素的数值,就将队列后端的数值弹出
            void push(int value){
                while(!que.empty()&&value>que.back()){
                    que.pop_back();
                }
                que.push_back(value);

            }
            int front(){
                return que.front();
            }
    };
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        MyQueue que;
        vector<int> result;
        for(int i = 0;i<k;i++){
            que.push(nums[i]);
        }
        //记录前k个元素中的最大值
        result.push_back(que.front());
        //从i=k开始,比从端口的前端开始方便记录,不过也差不多,在pop中有体现
        //后面的每次循环执行一次Myque函数中的操作
        for(int i=k;i<nums.size();i++){            
            que.pop(nums[i-k]);
            que.push(nums[i]);
            result.push_back(que.front());
        }
        return result;
    }
    
};

7.前K个高频元素

前K个高频元素
要统计元素出现的频率——map;
解决的问题:
对元素出现的频率进行统计;
对频率次数进行排序;
找出前K个高频元素。
需要补充的知识点:堆,堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值
统计前k个元素,小顶堆每次将最小的元素弹出,最后小顶堆的积累才是前k个最大元素。

class Solution {
public:
    //自定义一个比较函数,true不执行交换操作
    class mycomparsion{
    public:
        bool operator()(const pair<int,int>& lhs,const pair<int,int>& rhs){
            return lhs.second> rhs.second;
        }
    };

    vector<int> topKFrequent(vector<int>& nums, int k) {
        //用map统计元素出现的频率,map
        unordered_map<int,int> map; 
        for(int i = 0;i<nums.size();i++){
            map[nums[i]]++;
        }
        //对频率排序
        priority_queue<pair<int,int>,vector<pair<int,int>>,mycomparsion> pri_que;

        //用固定大小为k的小顶堆,扫描所有频率对应的数值
        for(unordered_map<int,int>::iterator it = map.begin();it!=map.end();it++){
            pri_que.push(*it);
            //如果堆的大小大于k,则队列弹出,保证堆的大小一直为k
            if(pri_que.size()>k){
                pri_que.pop();
            }
        }
        //找出前k个高频元素,小顶堆先弹出最小的,用倒序来输出到数组
        vector<int> result(k);
        for(int i = k-1;i>=0;i--){
            result[i] = pri_que.top().first;
            pri_que.pop();
        }
        return result;
    }
};

你可能感兴趣的:(算法,数据结构,leetcode)