LeetCode——栈和队列

本文详细介绍了循环队列的设计与实现,包括成员变量、主要操作以及相关问题的解决方案。此外,还探讨了广度优先遍历(BFS)在路径搜索中的应用,如打开转盘锁、每日温度计算、图像渲染等场景。同时,讲解了字符串解码、逆波兰表达式求值、用栈和队列实现特定功能,如字符串解码、用两个栈实现队列等。最后,讨论了如何使用队列和深度优先搜索解决01矩阵问题以及用栈实现栈和队列的转换。
摘要由CSDN通过智能技术生成

622. 设计循环队列

设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。

循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。
在这里插入图片描述

思路:

成员变量:

  • 队头指针head、当前队列中的元素数count、队列容量capacity、队列queue

主要操作:

  • MyCircularQueue(k): 构造器,设置队列长度为 k初始化各个成员变量。
  • Front: 从队首获取元素,直接通过head指针返回。如果队列为空,返回 -1 。
  • Rear: 获取队尾元素,利用 (head+count-1)%capacity来计算得到队尾的位置。如果队列为空,返回 -1 。
  • enQueue(value): 向循环队列插入一个元素,插入队尾,位置是 (head+count-1)%capacity,如果成功插入则返回真。
  • deQueue(): 从循环队列中删除一个元素,相当于将head指针向后移一位,但同时也要考虑下一位是否到了最后一个**(head+1)%capacity**,如果成功删除则返回真。
  • isEmpty(): 检查循环队列是否为空,看当前的元素数是否等于0。
  • isFull(): 检查循环队列是否已满,看当前元素数是否等于容量。

代码

class MyCircularQueue {
public:
    //队列头指针
    int head;
    //队列中元素数量
    int count;
    //队列容量
    int capacity;
    //用数组创建队列
    vector<int>queue;
    
    //构造函数
    MyCircularQueue(int k) {
        queue.resize(k);
        capacity=k;
        head=0;
        count=0;
    }
    //入队
    bool enQueue(int value) {
        if(isFull())
        return false;
        //循环队列,队尾位置以队头和元素数量和容量来推断
        queue[(head+count)%capacity]=value;
        count++;
        return true;
    }
    
    bool deQueue() {
        if(isEmpty())
        return false;
        //head向后移一个
        head=(head+1)%capacity;
        count--;
        return true;
    }
    
    int Front() {
        if(isEmpty())
        return -1;
        return queue[head];

    }
    
    int Rear() {
        if(isEmpty())
        return -1;
        return queue[(head+count-1)%capacity];
    }
    
    bool isEmpty() {
        return count==0;
    }
    
    bool isFull() {
        return count==capacity;
    }
};

/**
 * Your MyCircularQueue object will be instantiated and called as such:
 * MyCircularQueue* obj = new MyCircularQueue(k);
 * bool param_1 = obj->enQueue(value);
 * bool param_2 = obj->deQueue();
 * int param_3 = obj->Front();
 * int param_4 = obj->Rear();
 * bool param_5 = obj->isEmpty();
 * bool param_6 = obj->isFull();
 */

广度优先遍历(BFS)

应用场景:在一幅中找到从起点start到终点target的最近距离。

模板来自于题解

// 计算从起点 start 到终点 target 的最近距离
int BFS(Node start, Node target) {
    Queue<Node> q; // 核心数据结构
    Set<Node> visited; // 避免走回头路
    
    q.offer(start); // 将起点加入队列
    visited.add(start);
    int step = 0; // 记录扩散的步数

    while (q not empty) {
        int sz = q.size();
        /* 将当前队列中的所有节点向四周扩散 */
        for (int i = 0; i < sz; i++) {
            Node cur = q.poll();
            /* 划重点:这里判断是否到达终点 */
            if (cur is target)
                return step;
            /* 将 cur 的相邻节点加入队列 */
            for (Node x : cur.adj())
                if (x not in visited) {//已遍历过的就跳过
                    q.offer(x);
                    visited.add(x);
                }
        }
        /* 划重点:更新步数在这里 */
        step++;
    }
}
  • cur.adj()泛指 cur相邻的节点,比如说二维数组中,cur上下左右四面的位置就是相邻节点;
  • visited的主要作用是防止走回头路,遍历过的结点就跳过。大部分时候都是必须的(特别是在图中),但是像一般的二叉树结构,没有子节点到父节点的指针,不会走回头路就不需要 visited

752. 打开转盘锁

你有一个带有四个圆形拨轮的转盘锁。每个拨轮都有10个数字: ‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’ 。每个拨轮可以自由旋转:例如把 ‘9’ 变为 ‘0’,‘0’ 变为 ‘9’ 。每次旋转都只能旋转一个拨轮的一位数字。

锁的初始数字为 '0000' ,一个代表四个拨轮的数字的字符串。

列表deadends包含了一组死亡数字,一旦拨轮的数字和列表里的任何一个元素相同,这个锁将会被永久锁定,无法再被旋转。

字符串 target 代表可以解锁的数字,你需要给出最小的旋转次数,如果无论如何不能解锁,返回 -1
在这里插入图片描述

思路

  • 利用广度优先搜索
  • 以字符串‘0000’为起点,将它的每一位都分别加1和减1,总共有8种结果
  • 逐个考察这8个数,看是否是死亡数字或是目标数字,死亡数字的话就跳过,目标的话就返回当前层数。
  • 用一个哈希表来存储死亡数组和遍历过的数字,方便查询。
  • 利用队列,如果当前数字既不是死亡数组也没有遍历过的话就将它压入队列中
  • 本题的重点还在于如何“拨动”,让某一位数字加一和减一,比较巧妙,我不会,看代码吧。以下代码可实现0向后“拨动”变成99向前“拨动”变成0
// 完美的字符处理方式,利用ascⅡ码之差之后加上t并取余作为新得到的整型
//然后再加上0的ascⅡ码值返回字符
char a = (curr[j] -'0' + 10 + t) % 10 + '0';

代码

class Solution {
public:
    int openLock(vector<string>& deadends, string target) {
        // 哈希表vis中存储不可能入队的结点,包括deadends和已访问过的结点
        unordered_set<string> vis;
        vis.insert(deadends.begin(), deadends.end()); 

        if(vis.count("0000")) 
            return -1;
        //记步数
        int step = 0;
        //队列
        queue<string> st;
        //0000作为起始节点
        st.push("0000");
        //循环处理队列,直到队列中没有元素
        while(!st.empty()){            
            int length = st.size();
            for(int i = 0; i < length; i++){
                //取出队首元素
                string curr = st.front();
                st.pop();

                // 找到目标元素,直接返回答案
                if(curr == target)
                    return step;

                // 没有找到的话就继续向下看
                // 重点:处理curr周围的八个相邻结点,将没有在死亡数组中出现的数字加入到队列
                for(int j = 0; j < 4; ++j){
                    // 每一位自增1与自减1,所以有八种结果(4×2)
                    for(int t = -1; t < 2; t += 2){
                        // 完美的字符处理方式,利用ascⅡ码之差之后加上t并取余作为新得到的整型,然后再加上0的ascⅡ码值返回字符
                        char a = (curr[j] -'0' + 10 + t) % 10 + '0';
                        string newOne = curr;
                        newOne[j] = a;

                        // 若哈希集中找不到此状态,则加入哈希集同时入队
                        if(!vis.count(newOne)){
                            st.push(newOne);
                            vis.emplace(newOne);
                        }
                    }                 
                }
            }
            // 本层队列中元素处理完成,到达下一转动步数,步数加1
            step++;
        }
        return -1;
    }
};

739. 每日温度

请根据每日 气温 列表,重新生成一个列表。对应位置的输出为:要想观测到更高的气温,至少需要等待的天数。如果气温在这之后都不会升高,请在该位置用 0 来代替。

例如,给定一个列表temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]

提示:气温 列表长度的范围是[1, 30000]。每个气温的值的均为华氏度,都是在[30, 100]范围内的整数。

思路

  • 这道题要用到单调栈,栈中存放的是元素下标。
  • 先将第一个元素下标0入栈,然后遍历数组中的元素,逐个与栈顶数字所代表的数组元素进行比较。
  • 如果小于栈顶,就将当前元素下标入栈,如果大于的话,就弹出栈顶元素。也就是要保证从栈底到栈顶是单调递减的。
  • 我解释不清楚,还是看官方题解
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

代码

class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& T) {
        //利用单调栈存储元素下标
        //当前元素小于栈顶元素时,下标入栈
        //大于栈顶元素时,出栈,并在result数组对应位置赋值i-indx.top()
        stack<int>indx;
        vector<int>result(T.size(),0);
        for(int i=0;i<T.size();i++)
        {
            while(!indx.empty()&&T[i]>T[indx.top()])
            {
                result[indx.top()]=i-indx.top();
                indx.pop();
            }
            indx.push(i);
        }
        return result;
    }
};

150. 逆波兰表达式求值(栈)

根据 逆波兰表示法,求表达式的值。

有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。整数除法只保留整数部分。
在这里插入图片描述
在这里插入图片描述

思路

这道题有几个比较关键的地方

  1. 给定的表达式是string类型,对于整数字符串,应该将其转化为对应的整数,可以使用内置的函数atoi( ),在此之前还得先把string类型转变为char*类型,用c_str( )
  2. 遍历字符串数组,对每个字符串进行判断,看是否是运算符或是数字,将非+、-、*、/的字符串都认为是数字,对其处理后压入栈
  3. 如果是运算符,就从栈中弹出两个元素,用当前的运算符做运算,然后将计算结果压入栈。
  4. 最终的返回值是栈顶元素。

代码

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        //遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中。
        stack<int>st;
        for(string i:tokens)
        {
            if(!(i == "+" || i == "-" || i == "*" || i == "/"))
            {
                //压入数字
                //c_str() 以 char* 形式传回 string 内含字符串 
                //atoi()将字符串转化为对应整数
                st.push(atoi(i.c_str()));
            }
            else
            {
            	//弹出两个元素
                int num2=st.top();
                st.pop();
                int num1=st.top();
                st.pop();
                //switch的参数只能为char或int型,所以取字符串的第0个字符
                switch(i[0])
                {
                	//根据不同运算符做相应运算
                	//然后将结果压入栈
                    case '+':
                    st.push(num1+num2);
                    break;
                    case '-':
                    st.push(num1-num2);
                    break;
                    case '*':
                    st.push(num1*num2);
                    break;
                    case '/':
                    st.push(num1/num2);
                    break;
                }
            }
        }
        return st.top();
    }
};

394. 字符串解码(栈)

  • 给定一个经过编码的字符串,返回它解码后的字符串。
  • 编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。
  • 输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。
  • 原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a2[4] 的输入。
    示例

思路:

  • 利用辅助栈st和存储数字的变量multi,存储字符串的res,遍历字符串中每个字符,分四种情况
  • 如果当前字符是数字,那就将它解析成数值后放入multi中,作为重复的倍数,但是这里要注意,有可能出现多个连续的数字组成两位及其以上的整数,对multi要有一个累加的过程,这里的小技巧不太容易想到
  • 如果是字母,就将字母加到res后面
  • 如果是左括号'[',说明遇到了要重复的字符串,将遇到这个括号之前记录下来的resmulti压入栈st,然后将这两个变量之置0
  • 每个栈元素是一个int和一个string组成的对子,注意用pair<>make_pair()
  • 如果是右括号']',说明到了要重复的字符串的尾部,此时res中的字符串就是一个[ ]中的要重复的内容(因为[之前的字符在遇到[后被压入栈了)

代码

class Solution {
public:
    string decodeString(string s) {
        //利用辅助栈
        //注意使用pair<>来组成对子
        stack<pair<int,string>> st;
        int multi=0;
        string res="";
        //逐个读取字符串的字符
        for(int i=0;i<s.length();i++)
        {
            //数字
            if(s[i]>='0'&&s[i]<='9')
            {
                //可能有多位数字,要进行累加
                multi*=10;
                multi+=(s[i]-'0');
            }
            //如果是左括号,将左括号之前读到的数字和字符串入栈,注意压入栈后要置0
            else if(s[i]=='[')
            {
            	//注意使用make_pair()组成对子
                st.push(make_pair(multi,res));
                multi=0;
                res="";
            }
            //如果是右括号,弹出栈顶元素,res=栈顶res+当前读到的res*栈顶multi
            else if(s[i]==']')
            {
            	//取出栈顶元素的两个值,都是成员变量,不用加括号
            	//n是[]中字符串重复次数,3[a2[c]]中的2
            	//tmp是当前[]和上一个[之间的字符,比如3[a2[c]]中的a
                int n=st.top().first;
                string tmp=st.top().second;
                st.pop();
                //重复括号内的字符串,3[a2[c]]中的c
                while(n>0)
                {
                    tmp+=res;
                    n--;
                }
                res=tmp;
            }
            //如果是字母,加入到res,当遇到括号就将res暂时压入栈
            else
                res+=s[i];
        }
        return res;
    }
};

733. 图像渲染(遍历邻接点,队列)

一个图像(二维数组),每个元素的数值在065535之间,给定坐标(sr,sc),表示图像渲染开始的位置,以及一个新的颜色值newColor,如果该位置原本的值就等于新颜色值,那就不进行渲染,直接返回image
渲染从初始坐标开始,考察它的上下左右四个方向的值,如果等于初始坐标的值,就对他进行渲染,然后再看它的邻接点,返回经过上色渲染后的图像
在这里插入图片描述

思路

广度优先遍历

  • 用一个辅助队列来保存没有访问过并且值等于初始坐标值的点
  • 官方题解对于访问邻接点有个很好的方法,先定义两个数组
const int dx[4] = {1, 0, 0, -1};
const int dy[4] = {0, 1, -1, 0};
  • 然后只需要在中心坐标的基础上加数组元素,就能实现上下左右四个邻接点的遍历
for (int i = 0; i < 4; i++) {
                int mx = x + dx[i], my = y + dy[i];
                if (mx >= 0 && mx < n && my >= 0 && my < m && image[mx][my] == currColor) {
                    que.emplace(mx, my);
                    image[mx][my] = newColor;
                }
            }

代码

class Solution {
public:
    //用于访问邻接点
    const int dx[4] = {1, 0, 0, -1};
    const int dy[4] = {0, 1, -1, 0};
    vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int newColor) {
        int currColor = image[sr][sc];
        if (currColor == newColor) return image;
        int n = image.size(), m = image[0].size();
        //辅助队列
        queue<pair<int, int>> que;
        //初始点入队
        que.emplace(sr, sc);
        //渲染初始点
        image[sr][sc] = newColor;
        while (!que.empty()) {
            int x = que.front().first, y = que.front().second;
            que.pop();
            for (int i = 0; i < 4; i++) {
            	//考察当前位置的上下左右坐标
                int mx = x + dx[i], my = y + dy[i];
                //满足条件的结点入队
                if (mx >= 0 && mx < n && my >= 0 && my < m && image[mx][my] == currColor) {
                    que.emplace(mx, my);
                    image[mx][my] = newColor;
                }
            }
        }
        return image;
    }
};

深度优先搜索

class Solution {
public:
    //上下左右坐标
    int x[4]={1,0,0,-1};
    int y[4]={0,1,-1,0};
    //深度优先遍历
    void DFS(vector<vector<int>>& image,int sr, int sc, int newColor,int old,int r,int c)
    {
        image[sr][sc]=newColor;
        for(int i=0;i<4;i++)
        {
            int m_x=sr+x[i];
            int m_y=sc+y[i];
            if(m_x<r&&m_x>=0&&m_y<c&&m_y>=0)
            {
                if(image[m_x][m_y]==old)
                {
                    DFS(image,m_x,m_y,newColor,old,r,c);
                }
            }
        }
    }
    vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int newColor) {
        //深度优先,一条路走到黑
        if(image[sr][sc]==newColor)
        return image;
        int m=image.size();
        int n=image[0].size();
        int oldcolor=image[sr][sc];

        //启动深度优先遍历
        DFS(image,sr,sc,newColor,oldcolor,m,n);

        return image;
    }
};

542. 01 矩阵(队列)

给定一个由 0 和 1 组成的矩阵 mat ,请输出一个大小相同的矩阵,其中每一个格子是 mat 中对应位置元素到最近的 0 的距离。
两个相邻元素间的距离为 1
示例

思路

  • 参照官方题解
  • 题目要求是对于每个1,找到离他最近的0
  • 题目中可能存在多个0,为了找到离1最近的那个0,可以从0的位置开始进行广度优先搜索
  • 广度优先搜索可以找到从起点到其余所有点的 最短距离,因此如果我们从 0 开始搜索,每次搜索到一个 1,就可以得到 0 到这个 11 的最短距离
  • 对于多个0,将这些0全部加入队列中,并且作为已访问过的点
    算法

代码

class Solution {
public:
    int dx[4]={1,0,0,-1};
    int dy[4]={0,1,-1,0};
    vector<vector<int>> updateMatrix(vector<vector<int>>& mat) {
        //对矩阵中的每个1开启广度优先搜索,一层一层地向外扩张找0
        //开启的搜索次数是该1到最近的0的距离
        int r=mat.size();
        int c=mat[0].size();
        
        //结果矩阵
        vector<vector<int>>res(r,vector<int>(c,0));
        //已访问的结点
        vector<vector<int>>V(r,vector<int>(c,0));
        //辅助队列
        queue<pair<int,int>>Q;
        //遍历原矩阵的每个元素,将0加入队列
        for(int i=0;i<r;i++)
        {
            for(int j=0;j<c;j++)
            {
                if(mat[i][j]==0)
                {
                    Q.emplace(i,j);
                    V[i][j]=1;
                }
            }
        }
        
        while(!Q.empty())
        {
            int x=Q.front().first;
            int y=Q.front().second;
            Q.pop();
            for(int i=0;i<4;i++)
            {
                int mx=x+dx[i];
                int my=y+dy[i];
                //遇到没访问过得非0点
                if(mx>=0 && mx<r && my>=0 && my<c && !V[mx][my])
                {
                    res[mx][my]=res[x][y]+1;
                    Q.emplace(mx,my);
                    V[mx][my]=1;
                }
            }
        }
        return res;
    }
    //广度优先搜索,找0
    int BFS(vector<vector<int>>& mat,vector<vector<int>>& V,int nr,int nc)
    {
        int r=mat.size();
        int c=mat[0].size();
        //辅助队列
        queue<pair<int,int>>Q;
        Q.emplace(nr,nc);
        V[nr][nc]=1;
        int n=1;
        while(!Q.empty())
        {
            int x=Q.front().first;
            int y=Q.front().second;
            Q.pop();
            for(int i=0;i<4;i++)
            {
                int mx=x+dx[i];
                int my=y+dy[i];
                if(mx>=0 && mx<r && my>=0 && my<c && !V[mx][my])
                {
                    if(mat[mx][my]==1)
                    {
                        Q.emplace(mx,my);
                        V[mx][my]=1;
                    }
                    else if(mat[mx][my]==0)
                    return n;
                }
            }
            n++;
        }
        return 0;
    }
};

剑指 Offer 09. 用两个栈实现队列

两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTaildeleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )

思路

  • 简单题,一个输入栈S_in一个输出S_out
  • appendTail 将新元素直接压入输入栈,deleteHead则需要弹出的是输入栈的栈底元素,可以将输入栈中的所有元素依次弹出后再依次压入输出栈,此时输出栈的栈顶就是队列的头部。
  • 当输出栈非空时,每次deleteHead都直接从输出栈中弹出元素
  • 当输出栈为空而输入栈非空时,继续将输入栈中的全部元素弹出后依次加入输出栈作为补充
  • 当两个栈都为空时,返回-1

代码

class CQueue {
public:
    //两个辅助栈,输入栈和输出栈
    stack<int>S_in;
    stack<int>S_out;
    CQueue() {

    }
    
    void appendTail(int value) {
        //新元素压入输入栈
        S_in.push(value);
    }
    
    int deleteHead() {
        if(S_in.empty()&&S_out.empty())
        return -1;
        //将输入栈中元素弹出压入输出栈
        if(S_out.empty())
        {
            while(!S_in.empty())
            {
                S_out.push(S_in.top());
                S_in.pop();
            }
        }
        int res=S_out.top();
        S_out.pop();
        return res;
    }
};

/**
 * Your CQueue object will be instantiated and called as such:
 * CQueue* obj = new CQueue();
 * obj->appendTail(value);
 * int param_2 = obj->deleteHead();
 */

225. 用队列实现栈

请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。

实现 MyStack 类:
void push(int x) 将元素 x 压入栈顶。
int pop() 移除并返回栈顶元素。
int top() 返回栈顶元素。
boolean empty()如果栈是空的,返回 true ;否则,返回 false

思路:

  • 用一个辅助队列Q就可以实现
  • push压入元素时,直接压入Q
  • 然后将新加元素之前的所有数都依次出队然后再依次入队,这样的话,队首就是新压入的那个
  • 每次压入元素之后都将它前面的元素放出来再压进去
  • pop返回栈顶元素时,直接出队就好了,front同理
class MyStack {
public:
    queue<int>Q;
    /** Initialize your data structure here. */
    MyStack() {
    }
    
    /** Push element x onto stack. */
    void push(int x) {

        //将队列前面的数依次都出队,然后再依次重新加入队列
        //保证队首是新加的数
        int n=Q.size();
        Q.push(x);
        while(n>0)
        {
            int tmp=Q.front();
            Q.pop();
            Q.push(tmp);
            n--;
        }
    
    }
    
    /** Removes the element on top of the stack and returns that element. */
    int pop() {
        int tmp=Q.front();
        Q.pop();
        return tmp;
    }
    
    /** Get the top element. */
    int top() {
        return Q.front();
    }
    
    /** Returns whether the stack is empty. */
    bool empty() {
        return Q.empty();
    }
};

/**
 * Your MyStack object will be instantiated and called as such:
 * MyStack* obj = new MyStack();
 * obj->push(x);
 * int param_2 = obj->pop();
 * int param_3 = obj->top();
 * bool param_4 = obj->empty();
 */
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值