【栈和队列高频考点题】

目录

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;
}

猜你喜欢

转载自blog.csdn.net/m0_68872612/article/details/129616584