【力扣探索】队列和栈

本文介绍了队列和栈这两种数据结构及其在算法中的应用,如广度优先搜索(BFS)和深度优先搜索(DFS)。通过实例展示了如何使用队列进行BFS,如岛屿数量和打开转盘锁问题,以及如何使用栈进行DFS,如最小栈和有效括号问题。同时,文章提供了具体的模板和练习题目,帮助读者理解和掌握这两种数据结构的使用技巧。
摘要由CSDN通过智能技术生成

概述

对元素的处理有时候需要限定处理的顺序,这时候传统的数组和链表就无法满足了。
两种不同的处理顺序:先入先出和后入先出
两个相应的线性数据结构:队列和栈

队列:先入先出的数据结构

简介

  1. 在 FIFO 数据结构中,将首先处理添加到队列中的第一个元素。
  2. 队列是典型的 FIFO 数据结构。插入(insert)操作也称作入队(enqueue),新元素始终被添加在队列的末尾。 删除(delete)操作也被称为出队(dequeue)。 你只能移除第一个元素。

实现

  1. 动态数组+起始索引标记,实现简单,当开始标记移动时,会造成空间的浪费
  2. 使用循环队列的方式实现

循环队列

使用固定大小的数组和两个指针来指示起始位置和结束位置,重用之前被浪费的存储

基本操作

queue q;
入队:q.push(3);
出队:q.pop();
获取第一个元素:q.front();
获取最后一个元素:q.back();
获取尺寸:q.size();
是否为空:q.empty();

广度优先搜索BFS

广度优先搜索(BFS)的一个常见应用是找出从根结点到目标结点的最短路径,另一个是遍历

  1. 结点的处理顺序
    第一轮:根结点
    第二轮:根结点旁边的结点
    第三轮:根结点两步的结点

    与树的层序遍历类似,越是接近根结点的结点将越早地遍历。
    如果在第 k 轮中将结点 X 添加到队列中,则根结点与 X 之间的最短路径的长度恰好是 k。也就是说,第一次找到目标结点时,你已经处于最短路径中。

  2. 队列的入队和出队顺序
    1.首先将根结点排入队列。在每一轮中,我们逐个处理已经在队列中的结点,并将所有邻居添加到队列中。值得注意的是,新添加的节点不会立即遍历,而是在下一轮中处理。
    2.结点的处理顺序与它们添加到队列的顺序是完全相同的顺序,即先进先出(FIFO)。这就是我们在 BFS 中使用队列的原因。

  3. 在特定问题中执行 BFS 之前确定结点和边缘非常重要。通常,结点将是实际结点或是状态,而边缘将是实际边缘或可能的转换。

模板1

/* Return the length of the shortest path between root and target node. */
int BFS(Node root, Node target) {
   
    Queue<Node> queue;  // store all nodes which are waiting to be processed
    int step = 0;       // number of steps neeeded from root to current node
    // initialize
    add root to queue;
    // BFS
    while (queue is not empty) {
   
        step = step + 1;
        // iterate the nodes which are already in the queue
        int size = queue.size();
        for (int i = 0; i < size; ++i) {
   
            Node cur = the first node in queue;
            return step if cur is target;
            for (Node next : the neighbors of cur) {
   
                add next to queue;
            }
            remove the first node from queue;
        }
    }
    return -1;          // there is no path from root to target
}
  1. 在每一轮中,队列中的结点是等待处理的结点。
  2. 在每个更外一层的 while 循环之后,我们距离根结点更远一步。变量 step 指示从根结点到我们正在访问的当前结点的距离。

模板2

有时,确保我们永远不会访问一个结点两次很重要。否则,我们可能陷入无限循环。如果是这样,我们可以在上面的代码中添加一个哈希集来解决这个问题。

/* Return the length of the shortest path between root and target node.*/
int BFS(Node root, Node target) {
   
    Queue<Node> queue;  // store all nodes which are waiting to be processed
    Set<Node> used;     // store all the used nodes
    int step = 0;       // number of steps neeeded from root to current node
    // initialize
    add root to queue;
    add root to used;
    // BFS
    while (queue is not empty) {
   
        step = step + 1;
        // iterate the nodes which are already in the queue
        int size = queue.size();
        for (int i = 0; i < size; ++i) {
   
            Node cur = the first node in queue;
            return step if cur is target;
            for (Node next : the neighbors of cur) {
   
                if (next is not in used) {
   
                    add next to queue;
                    add next to used;
                }
            }
            remove the first node from queue;
        }
    }
    return -1;          // there is no path from root to target
}

有两种情况你不需要使用哈希集:

  1. 你完全确定没有循环,例如,在树遍历中;
  2. 你确实希望多次将结点添加到队列中。

练习

1. 岛屿数量BFS

题目链接: 200. 岛屿数量
解题思路

  1. BFS策略,访问过的元素记得标记防止无限相互回溯。
    代码
class Solution {
   
public:
    int numIslands(vector<vector<char>>& grid) {
   
    	int row = grid.size();
    	int col = grid[0].size();
    	vector<int> map(col*row, 0);
    	int numscount = 0;
    	for(int i = 0; i < row; ++i){
   
    		for(int j = 0; j < col; ++j){
   
    			if(grid[i][j] == '1' && map[i*col+j] == 0){
   
    				numscount++;
    				islandBFS(i, j, map, grid, numscount);
    			}
    		}
    	}
    	return numscount;
    }
    void islandBFS(int geti, int getj, vector<int>& map, vector<vector<char>>& grid, int num){
   
    	int row = grid.size();
    	int col = grid[0].size();
    	queue<int> mapindex;
    	map[geti*col+getj] = num;
    	mapindex.push(geti*col+getj);
    	while(!mapindex.empty()){
   
    		int i = mapindex.front() / col;
    		int j = mapindex.front() % col; 		
    		mapindex.pop();
    		if(i-1 >= 0 && grid[i-1][j] == '1' && map[(i-1)*col+j] == 0){
   
    			mapindex.push((i-1)*col+j);
    			map[(i-1)*col+j] = num;			
    		} 
    		if(i+1 < row && grid[i+1][j] == '1' && map[(i+1)*col+j] == 0){
   
    			mapindex.push((i+1)*col+j);
    			map[(i+1)*col+j] = num;  			
    		}
    		if(j-1 >= 0 && grid[i][j-1] == '1' && map[i*col+j-1] == 0){
   
    			mapindex.push(i*col+j-1);
    			map[i*col+j-1] = num;		
    		} 
    		if(j+1 < col && grid[i][j+1] == '1' && map[i*col+j+1] == 0){
   
    			mapindex.push(i*col+j+1);
    			map[i*col+j+1] = num;			
    		}
    	}

    }
};

2. 打开转盘锁

题目链接: 752. 打开转盘锁
解题思路

  1. 广度优先遍历的总体思想
  2. 具体实施很多细节可以学习,判断一个字符串是否在一个字符串数组中,可以转化为unordered_set进行处理,提供count(string s)函数可以判断是否存在,另外在构建时利用迭代器可以一步复制原来的字符串数组到set当中。
  3. 字符char和整形int的相互转换,int到char可以 char c = i + ‘0’, char到int可以 int i = c - ‘0’,需要了解char的编码规则

代码

class Solution {
   
public:
    int openLock(vector<string>& deadends, string target) {
   
    	unordered_set<string> deadendset;
    	deadendset.insert(deadends.begin(), deadends.end());
    	if(deadendset.count("0000")) return -1;
    	queue<string> sq;
    	sq.push("0000");
    	int res = 0;
    	while(!sq.empty()){
   
    		int size = sq.size(); //帮助我们统计离出发点的层数,这里的size控制的for循环必不可少
    		for(int t = 0; t < size; ++t){
    
    			string temp = sq.front();
    			sq.pop();
    			if(temp == target) return res;
    			for(int i = 0; i < 4; ++i){
   
    				for(int j = -1; j < 2; j += 2){
   
    					string newtemp = temp;
    					int vali = (temp[i]-'0' + j + 10) % 10; // +10保证减法操作也可以统一在这个式子中
    					newtemp[i] = vali + '0';
    					if(!deadendset.count(newtemp)){
   
    						sq.push(newtemp);
    						deadendset.insert(newtemp);
    					};
    				}
    			}
    		}
    		res
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值