【栈和队列高频考点题】

news2024/12/25 14:58:08

目录

1 与栈有关的考题

1.1 最小栈

1.2 栈的弹出压入序列 

1.3 逆波兰表达式求值

1.4 二叉树的最近公共祖先 

1.5 单调栈

2 与队列有关的考题

2.1 二叉树的分层遍历

2.2 滑动窗口


1 与栈有关的考题

1.1 最小栈

题目描述:

 解题思路:

要想在O(1)时间内获得栈内最小元素只用一个栈肯定是行不通的,所以我们不妨再开一个栈来记录当前栈里面每个元素的最小值,这样就要多开出O(N)的空间,可以优化空间的方法是并不是所有的元素都要入最小栈,而是当前元素比最小栈栈顶的数据大便不必再入数据了,但是等于时一定要入进去,当pop掉元素时只需要比较普通栈与最小栈栈顶元素是否相等,若相等,则最小栈栈顶数据也得pop。

 参考代码:

class MinStack {
public:
    MinStack() {}
    
    void push(int val) {
        _st.push(val);
        if(_minSt.empty() || _minSt.top()>=val)
            _minSt.push(val);
    }
    
    void pop() {
         if(_minSt.top()==_st.top())
            _minSt.pop();
         _st.pop();
       
    }
    
    int top() {
        return _st.top();
    }
    
    int getMin() {
        return _minSt.top();
    }
    stack<int> _st;
    stack<int> _minSt;//记录最小的数据
};

 运行结果:

 题后反思:

万一数据像这种22222222,貌似我们优化空间的方法起不到作用了,可以采取的优化措施是我们存储数据时可以存一个pair,里面额外记录一下变量的个数,当有重复数据时便不再入数据了,而是++里面的计数器,pop时就--里面的计数器,大家有兴趣可以自己实现下,这里我就不再实现了。


1.2 栈的弹出压入序列

题目描述:

 解题思路:

这道题的解决方法有很多种,这里分享一种比较容易理解的思路:另外搞一个栈,将栈的压入序列不断的push到这个栈中,当该栈不为空并且栈的弹出序列与该栈栈顶数据相等时就pop掉该栈栈顶数据,然后不断迭代走,最后返回值就是该栈是否为空或者是否走到了弹出序列的尾。

参考代码:

class Solution {
public:
    bool IsPopOrder(vector<int> pushV,vector<int> popV) {
        stack<int> v;
        int pushi=0,popi=0;
        while(pushi<pushV.size())
        {
            v.push(pushV[pushi++]);
            while(!v.empty() && v.top()==popV[popi])
            {
                v.pop();
                popi++;
            }
        }
        return v.empty();
    }
};

运行结果:


1.3 逆波兰表达式求值

题目描述:

 解题思路:

由于逆波兰表达式就是后缀表达式,运算符的优先级已经确定了,所以我们只需要模拟下运算的过程即可,注意先取的数据是右,后取的数据为左。

 参考代码:

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> s;
        for(auto& e:tokens)
        {
    
            if(e=="+" || e=="-" || e=="*" || e=="/")
            {
                int right=s.top();
                s.pop();
                int left=s.top();
                s.pop();
                switch(e[0])
                {
                    case '+':
                        s.push(left+right);
                        break;
                    case '-':
                        s.push(left-right);
                        break;
                    case '*':
                        s.push(left*right);
                        break;
                    case '/':
                        s.push(left/right);
                        break;
                }
            }
            else
            {
                s.push(stoi(e));
            }
        }
        return s.top();
    }
};

运行结果:

 题后反思:

假如给的不是后缀表达式,而是中缀表达式,如何将中缀表达式转化为后缀表达式呢?

给定中缀表达式【1+2*(3-4)+5/6】,我们如何将其转化为后缀表达式?

基本规则是这样的:当遇到操作数的时候我们直接输出;遇到操作符当栈为空就直接入栈,栈若不为空就与栈顶数据相比,若优先级大于栈顶就将该操作符入栈(由于后面操作符优先级可能更大,所以先让其入栈),若优先级小于或者等于栈顶,就将栈顶数据pop掉然后输出,直到栈为空或者遇到优先级更小的操作符,然后继续迭代直到访问到所有元素。

但是这个规则只适用于没有()的转换中,例如【1+2*3-4】可以转化成【1 2 3 * + 4 -】

但是像上面的那个栗子【1+2*(3-4)+5/6】貌似就不适用了,处理方法有两种:

  • 第一种是不入()到栈中,当发现出现了()时就认为()中的运算符优先级非常大,就让其入栈,其他规则与基本规则类似。
  • 第二种是将()入栈,当出现(时就认为(的优先级非常低,(目的是让括号中运算符入栈),当出现)时同样认为)的优先级非常低,(目的是让括号中运算符出栈输出)

无论使用哪种规则,我们都能够很轻易的将上述中缀表达式转化为:

【1 2 3 4 - * + 5 6 / +】


1.4 二叉树的最近公共祖先

题目描述:

 解题思路1:

基本思路是通过根节点的左子树和右子树分别找p和q,然后再递归去找。

 参考代码:

class Solution {
public:
    bool find(TreeNode* root, TreeNode* x)
    {
        if(root==nullptr)
            return false;
        if(root==x)
            return true;
        return find(root->left,x) || find(root->right,x);
    }

    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(root==nullptr)
            return nullptr;
        if(root==p || root==q)
            return root;

        bool pInLeft,pInRight,qInLeft,qInRight;
        pInLeft=find(root->left,p);
        pInRight=!pInLeft;
        qInLeft=find(root->left,q);
        qInRight=!qInLeft;

        if((pInLeft && qInRight) || (pInRight && qInLeft))
            return root;
        else if(pInLeft && qInLeft)
            return lowestCommonAncestor(root->left,p,q);
         else if(pInRight && qInRight)
            return lowestCommonAncestor(root->right,p,q);
        else 
            return nullptr;
    }
};

题后反思:

这种方式如果该树大致是一个单叉树时效率就很低下了,这样时间复杂度大概是N^2量级的,有什么更好的解决思路吗?

解题思路2:

我们可以用两个栈来分别存储查找p和q的路径,然后转化为链表相交问题,这样时间复杂度是N量级的。

参考代码:

class Solution {
public:
    //记录路径,最坏情况也是O(N)
    bool findPath(TreeNode* root, TreeNode* x, stack<TreeNode*>& Path)
    {
        if(root==nullptr)
            return false;
        Path.push(root);//先把结点入进去
        if(root==x)
            return true;
        if(findPath(root->left,x,Path))//如果从左子树中找到了就返回
            return true;
        if(findPath(root->right,x,Path))//如果从右子树中找到了就返回
            return true;

        //走到这里说明左子树没有找到,右子树也没有找到,就pop掉栈顶
        Path.pop();
        return false;
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        stack<TreeNode*> pPath;
        stack<TreeNode*> qPath;
        findPath(root,p,pPath);
        findPath(root,q,qPath);

        //让大的先走差距步
        while(pPath.size()!=qPath.size())
        {
            if(pPath.size()>qPath.size())
                pPath.pop();
            else
                qPath.pop();
        }
        while(pPath.top()!=qPath.top())
        { 
            pPath.pop();
            qPath.pop();
        }
        return pPath.top();
    }
};

运行结果:


1.5 单调栈

题目描述:

 解题思路:

单调栈模型,相信大家能够轻松AC.

参考代码:

自己写一个栈:

#include<iostream>
using namespace std;
const int N=1e5+10;
int st[N],tt;
int main()
{
    int n;
    scanf("%d",&n);
   while(n--)
   {
       int x;
       scanf("%d",&x);
       while(tt && st[tt]>=x) tt--;
       if(!tt)  printf("-1 ");
       else     printf("%d ",st[tt]);
       st[++tt]=x;
   }
    return 0;
}

使用STL中stack:

#include<iostream>
#include<stack>
using namespace std;
int main()
{
    int n;
    scanf("%d",&n);
    stack<int> st;
    while(n--)
    {
        int x;
        scanf("%d",&x);
        while(!st.empty() && st.top()>=x) st.pop();
        if(st.empty()) printf("-1 ");
        else printf("%d ",st.top());
        st.push(x);
    }
    return 0;
}

2 与队列有关的考题

2.1 二叉树的分层遍历

题目描述:

 解题思路:

这种题我们能一眼看出来是用队列做,但是本题的难点在于怎样确定每层结点个数。我们不妨用一个变量levelSize计数每层结点个数,当pop掉该层结点push新的结点后及时更新levelSize。

 参考代码:

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> vv;
        queue<TreeNode*> q;
        if(root==nullptr)
            return vv;
        int levelSize=1;
        q.push(root);
        while(!q.empty())
        {
            vector<int> v;
            while(levelSize--)
            {
            TreeNode* front=q.front();
            v.push_back(front->val);
            q.pop();
            if(front->left)
                q.push(front->left);
            if(front->right)
                q.push(front->right);
            }
            levelSize=q.size();
            vv.push_back(v);
        }
        return vv;
    }
};

2.2 滑动窗口

题目描述:

 解题思路:

这也是一个简单的基础模板题,就直接给大家看代码了。

 自己实现一个:

#include<iostream>
using namespace std;
const int N=1e6+10;

int a[N],q[N];

int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++) scanf("%d",&a[i]);
    int hh=0,tt=-1;
    for(int i=0;i<n;i++)
    {
        if(hh<=tt && i-m+1>q[hh])   ++hh;//q[hh]不在窗口[i-m,i-1]内就出队
        while(hh<=tt && a[i]<=a[q[tt]]) --tt;//当前值<=队尾值,队尾出队(双端队列)
        q[++tt]=i;//队列中入的是下标目的是为了方便队头出队
        if(i-m+1>=0) printf("%d ",a[q[hh]]);//使用队头(最小值)
    }
    puts("");
    hh=0,tt=-1;
    for(int i=0;i<n;i++)
    {
        if(hh<=tt && i-m+1>q[hh])   ++hh;
        while(hh<=tt && a[i]>=a[q[tt]]) --tt;
        q[++tt]=i;
        if(i-m+1>=0) printf("%d ",a[q[hh]]);
    }
}

采用STL的deque(双端队列):

#include<iostream>
#include<deque>
using namespace std;
const int N=1e6+10;

int a[N];

int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=0;i<n;i++) scanf("%d",&a[i]);
    deque<int>q;//双向队列
    for(int i=0;i<n;i++)
    {
        if(!q.empty() && i-m+1>q.front()) q.pop_front();
        while(!q.empty() && a[i]<=a[q.back()]) q.pop_back();
        q.push_back(i);
        if(i-m+1>=0) printf("%d ",a[q.front()]);
    }
    puts("");
    q.clear();//记得要清理
    for(int i=0;i<n;i++)
    {
        if(!q.empty() && i-m+1>q.front()) q.pop_front();
        while(!q.empty() && a[i]>=a[q.back()]) q.pop_back();
        q.push_back(i);
        if(i-m+1>=0) printf("%d ",a[q.front()]);
    }
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/421940.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

微信小程序引入 vant ui组件

1.初始化 在小程序根目录&#xff08;app.js所在目录&#xff09;&#xff0c;打开cmd命令窗口 npm init -y参数 -y 表示对 npm 要求提供的信息&#xff0c;都自动按下回车键&#xff0c;表示接受默认值。 2.下载miniprogram依赖 通过 npm 安装: npm i vant/weapp -S --p…

京东开源RaftKeeper性能超越ZooKeeper!

一、背景介绍 成百上千台服务器组成的分布式系统中&#xff0c;服务器故障或网络抖动会随时发生&#xff0c;有时会导致严重的系统崩溃&#xff0c;为解决如上问题&#xff0c;雅虎开源了ZooKeeper分布式协调服务并在2010年成为Apache顶级项目&#xff0c;是Hadoop、HBase和Cl…

为什么FTP会随着时间的过去而变慢?

有人问&#xff1a;我在XP上有FZ客户端3.5.3&#xff0c;在Vista上有0.9.41服务器。通过已经很慢的连接传输大文件时&#xff0c;我注意到速度开始时约为40kb / s&#xff0c;但逐渐趋于稳定&#xff0c;约为20kb / s&#xff0c;并保持这种状态。如果我退出客户端并重新启动它…

夜天之书 #81 大厂开源之殇

本轮开源之风吹起迄今数年&#xff0c;最大的影响还是越来越多的商业公司开始探索开源方法能够如何改变自己的经营策略。开源策略循序渐进分成使用、参与和发起。在发起开源项目实践一线的&#xff0c;一个是打着开源旗号的创业公司&#xff0c;另一个就是大型企业尤其互联网企…

JUC并发编程之AQS原理

1. AQS 原理 1.1 概述 全称是 AbstractQueuedSynchronizer&#xff0c;是阻塞式锁和相关的同步器工具的框架 特点&#xff1a; 用 state 属性来表示资源的状态&#xff08;分独占模式和共享模式&#xff09;&#xff0c;子类需要定义如何维护这个生态&#xff0c;控制如何获…

剪枝与重参第六课:基于VGG的模型剪枝实战

目录基于VGG的模型剪枝实战前言1.Intro2.Prune实战2.1 说明2.2 test()2.3 加载稀疏训练模型2.4 前处理2.5 建立新模型并存储信息2.6 BatchNorm层的剪枝2.7 Conv2d的剪枝2.8 Linear的剪枝3.基于VGG的模型剪枝总结基于VGG的模型剪枝实战 前言 手写AI推出的全新模型剪枝与重参课程…

快排的递归实现

快速排序是一种时间复杂度低&#xff0c;但会虽随着数组的顺序变化&#xff0c;因为其效率之高被称为快速排序&#xff0c;而 且其不稳定性也可以同过优化进行解决。 快速排序的实现有三种方法&#xff1a; 1.hoare版 其基本思想为&#xff1a;任取待排序元素序列中 的某元…

3、如何使用GDB来进行命令行debug

文章目录一、与前面的联系二、GDB的一些认识1、什么是gdb2、gdb作用3、gdb可实现的功能三、GDB常用的调试命令一、与前面的联系 对于前面说到的launch.json文件就是用于debug的配置文件&#xff0c;在前面的vscode中我们可以发现配置好launch.json文件之后进行调试&#xff0c…

攻防世界-web2(逆向加密算法)

打开链接是PHP源码 给了一串密文&#xff0c;并对这串密文进行了一系列操作加密&#xff0c;注释里说解密$miwen就是flag 在此我们先介绍一些PHP内置函数&#xff1a; strrev(string): 反转字符串 strlen(string): 返回字符串的长度 substr(string, start, length): 返回字符…

认识、使用C++vetor和array

目录 前言&#xff1a; 1.vector模板 1.1vector简介 1.2创建vector类对象 2.array模板 2.1array简介 2.2创建array类对象 3.比较中学习 4.怎么避免数组越界访问 前言&#xff1a; 指针的基础用法分了近三篇文章&#xff0c;结合数组、结构、共用体、字符串一起学习。相…

【Golang | http】使用http库完成一个简单的POST请求

引言 主要记录使用Golang实现一个POST请求所用到的小知识点 1、项目结构 客户端向服务端注册用户信息&#xff0c;服务端返回注册信息中的用户名 PS E:\goland-workspace\GolangLearning\http> tree /f 卷 文件 的文件夹 PATH 列表 卷序列号为 0C66-1433 E:. ├─client…

小样本学习FSL介绍

1 概念 小样本学习&#xff08;few-shot learning&#xff0c;FSL&#xff09;旨在从有限的标记实例&#xff08;通常只有几个&#xff09;中学习&#xff0c;并对新的、未见过的实例进行识别。 相比于传统的深度学习和机器学习方法&#xff0c;小样本学习能够更好地模拟人类的…

从C出发 22 --- 变量的作用域与生命期

问题 1 &#xff1a; 这样子定义一个不属于任何函数的变量正确吗? 问题 2 : 编译能通过吗? 问题 : 我们要打印的var 到底是 10 还是 100. 总结: 什么都不会输出&#xff0c;因为这里的 i ;是让局部变量的 i &#xff0c;程序会一直死循环 为什么都是 11&#xff0c;为什…

在构建个人想法时,使用哪个工具更好呢?Tana, AmpleNote 和 妙记多 Mojidoc的比较

笔记类 App 都很强调个人化&#xff0c;因为我们每个人会用不同的方法来做笔记、写日记。不过有一些框架可以帮助我们&#xff0c;比如子弹笔记&#xff08;Bullet Journal&#xff09;等。 Tana 和 Amplenote 都可以使用「标签」&#xff0c;尽管它们处理的方式、体验都大不相…

4.14~4.16学习总结

多线程&#xff1a; 同步代码块 格式&#xff1a;Synchronized(锁) { 操作共享数据的代码 } 特点1&#xff1a;锁默认打开&#xff0c;有一个线程进去了&#xff0c;锁自动关闭。 特点2&#xff1a;里面的代码全部执行完毕&#xff0c;线程处理&#xff0c;锁自动打开。 …

SaleSmartly(ss客服)怎么玩转Instagram自动化?

这段时间接触了不少粉丝&#xff0c;一直在说ins营销&#xff0c;说谁谁谁通过这个引流&#xff0c;结果爆了&#xff0c;那我们今天就来简单说一下。Instagram (IG) 是全球最大的照片和视频共享平台&#xff0c;拥有超过10亿的月活跃用户和 5 亿的日活跃Story用户。借助IG的强…

LNMP和论坛的搭建

系列文章目录 文章目录系列文章目录一、LNMP搭建1.承接上文搭建nginx服务2.Mysql数据库搭建3.安装配置 PHP 解析环境4.、部署 Discuz&#xff01;社区论坛 Web 应用总结一、LNMP搭建 1.承接上文搭建nginx服务 2.Mysql数据库搭建 1、安装Mysql环境依赖包 yum -y install \ n…

不限量免注册,极速体验AI助手

最近 ChatGPT 很火&#xff0c;火到每个人都想玩一把&#xff0c;由于受限&#xff0c;不是在搭梯子就是在搭梯子的路上&#xff0c;现在类 ChatGPT 产品&#xff0c;它终于来了。还是先简单秀一波操作&#xff1a;第一波&#xff1a;大数据记录中&#xff0c;涉及关键字快速检…

vue2路由(上)

路由的简介 什么是路由&#xff1f; 用生活上的例子&#xff0c;路由器上的接口对应一个主机。 而由key和values组成的映射关系就是路由 主要用于SPA单页面应用 就是根据你端口号后面的路径&#xff0c;看你有没有配置这个页面对应的组件&#xff0c;如果有&#xff0c;那么就…

批处理脚本用法总结

目录一、常用命令二、基本语法1. rem 和 ::2. echo 和 3. pause4. errorlevel5. title6. color7. goto 和 :三、常见用法1. 设置临时环境变量2. 启动CMD执行命令3. 打开环境变量窗口参考资料&#xff1a;批处理(Batch)&#xff0c;也称为批处理脚本。顾名思义&#xff0c;批处理…