【C++】BFS类型题目总结

简介: BFS类型题目总结

1、墙与门

题目连接


题目描述:

ed6afd50cf554912af5b5f3f2ce8d3ed.png

注意:

m == rooms.length

n == rooms[i].length

1 <= m, n <= 250

rooms[i][j] 是 -1、0 或 231 - 1

解题思路:

核心在于用队列把只存门和空房间的位置,把墙排除在外。刚好门的值为0,这样拿出队列里的每一个元素,搜索它的上下左右位置有没有空房间,有的话更新空房间的最近门距离就是该元素的值+1,之后把空房间的位置也入队列,继续拿出队列的下一个元素去更新它周围空房间的值。


遍历数组,把所有门的下标入队列。

依次拿出队列的元素,检查该元素的上下左右位置是否有空房间,有的话空房间的值改为:刚从队列拿出元素的值 + 1,就是该位置空房间的最近的门距离,之后把空房间的下标也入队列。重复前面的动作直到队列为空。

完整代码:

class Solution 
{
private:
    int around[4][2] = 
        {
            {1, 0},
            {-1, 0},
            {0, 1},
            {0, -1}
        };
public:
    void wallsAndGates(vector<vector<int>>& rooms) 
    {
        queue<pair<int, int>> q;
        int m = rooms.size();
        int n = rooms[0].size();
        // 1、先把所有的门都入队列
        for(int i=0; i<m; ++i)
        {
            for(int j=0; j<n; ++j)
            {
                if(rooms[i][j] == 0)
                {
                    q.push(make_pair(i, j));
                }
            }
        }
        // 2、依次拿出队列里的坐标,看看该位置的上下左右是否有空房间
        // 如果有空房间,那么空房间的最近门距离就是该位置的值+1,完成后原来空房间的坐标入队列
        while(!q.empty())
        {
            pair<int, int> pr = q.front();
            q.pop();
            int row = pr.first;
            int col = pr.second;
            for(int i=0; i<4; ++i)
            {
                int newRow = row + around[i][0];
                int newCol = col + around[i][1];
                if(newRow<0 || newRow>=m || newCol<0 || newCol>=n || rooms[newRow][newCol] != INT_MAX)
                {
                    continue;
                }
                rooms[newRow][newCol] = rooms[row][col] + 1;
                q.push(make_pair(newRow, newCol));
            }
        }
    }
};


性能分析:


时间复杂度:O(m*n)。

空间复杂度:O(m*n)。最坏情况全部都是门,全部入队列


2、图像渲染

图像渲染

题目描述:

有一幅以二维整数数组表示的图画,每一个整数表示该图画的像素值大小,数值在 0 到 65535 之间。


给你一个坐标 (sr, sc) 表示图像渲染开始的像素值(行 ,列)和一个新的颜色值 newColor,让你重新上色这幅图像。


为了完成上色工作,从初始坐标开始,记录初始坐标的上下左右四个方向上像素值与初始坐标相同的相连像素点,接着再记录这四个方向上符合条件的像素点与他们对应四个方向上像素值与初始坐标相同的相连像素点,……,重复该过程。将所有有记录的像素点的颜色值改为新的颜色值。


最后返回经过上色渲染后的图像。


示例:


输入:

image = [[1,1,1],[1,1,0],[1,0,1]]

sr = 1, sc = 1, newColor = 2

输出:

[[2,2,2],[2,2,0],[2,0,1]]

解析:

在图像的正中间,(坐标(sr,sc)=(1,1)),

在路径上所有符合条件的像素点的颜色都被更改成2。

注意,右下角的像素没有更改为2,

因为它不是在上下左右四个方向上与初始点相连的像素点。


注意:


image 和 image[0] 的长度在范围 [1, 50] 内。

给出的初始点将满足 0 <= sr < image.length 和 0 <= sc < image[0].length。

image[i][j] 和 newColor 表示的颜色值在范围 [0, 65535]内。

解题思路:

给定一个位置,记该位置颜色为curColor,把这个位置附近的所有这个curColor颜色的区域块染成newColor。典型的单路径BFS,注意搜索前要先把当前位置的颜色染成newColor。


完整代码:

class Solution 
{
    int distance[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
public:
    vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int newColor) 
    {
        // 1、创建一个存储下标的队列,把(sr,sc)先入队列,从这里开始搜索
        int curColor = image[sr][sc];
        if(curColor == newColor)
        {
            return image;
        }
        int row = image.size();
        int col = image[0].size();
        queue<pair<int, int>> q;
        q.push(make_pair(sr, sc));
        image[sr][sc] = newColor;
        // 2、BFS向上下左右搜索curColor
        while(!q.empty())
        {
            pair<int, int> front = q.front();
            q.pop();
            int i = front.first;
            int j = front.second;
            for(int d = 0; d < 4; ++d)
            {
                int ni = i + distance[d][0];
                int nj = j + distance[d][1];
                if(ni>=0 && ni<row && nj>=0 && nj<col && image[ni][nj]==curColor)
                {
                    q.push(make_pair(ni, nj));
                    image[ni][nj] = newColor;
                }
            }
        }
        // 3、最后返回图像
        return image;
    }
};


性能分析:

时间复杂度:O(n*m)。最坏情况这个矩阵颜色都是curColor。

空间复杂度:O(n*m)。队列的开销。


3、01 矩阵

题目连接


题目描述:

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


两个相邻元素间的距离为 1 。

f9ab6d33f8804cceae31c184819d1c5c.png

6af5d604897c4debafdae4b41223758e.png


注意:


m == mat.length

n == mat[i].length

1 <= m, n <= 104

1 <= m * n <= 104

mat[i][j] is either 0 or 1

mat 中至少有一个 0

解题思路:

多路径源的搜索问题,考虑创建一个超级0来作为搜索时的标识。一开始把原矩阵中所有值为0的下标入队列,然后BFS搜索。


完整代码:

class Solution 
{
    int constExr[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
public:
    vector<vector<int>> updateMatrix(vector<vector<int>>& mat) 
    {
        // 1、创建
        // 最终返回的结果矩阵ret
        // 用来标志多路径源集合的矩阵super,一开始把mat中值为0的下标对应到super中标志为1
        // bfs时用到的存储下标键值对的队列,一开始把所有值为0的下标入队列
        int row = mat.size();
        int col = mat[0].size();
        vector<vector<int>> ret(row, vector<int>(col));
        vector<vector<int>> super(row, vector<int>(col));
        queue<pair<int, int>> q;
        for(int i = 0; i < row; ++i)
        {
            for(int j = 0; j < col; ++j)
            {
                if(mat[i][j] == 0)
                {
                    q.push(make_pair(i, j));
                    super[i][j] = 1;
                }
            }
        }
        // 2、进行多路径的广度优先搜索
        while(!q.empty())
        {
            pair<int, int> front = q.front();
            int i = front.first;
            int j = front.second;
            q.pop();
            for(int d = 0; d < 4; ++d)
            {
                int ni = i+constExr[d][0];
                int nj = j+constExr[d][1];
                if(ni>=0 && ni<row && nj>=0 && nj<col && !super[ni][nj])
                {
                    q.push(make_pair(ni, nj));
                    ret[ni][nj] = ret[i][j] + 1;
                    super[ni][nj] = 1;
                }
            }
        }
        // 返回结果矩阵ret
        return ret;
    }
};


性能分析:

时间复杂度:O(n*m)。最坏情况遍历整个数组。

空间复杂度:O(n*m)。队列的开销。


4、岛屿数量

题目连接


题目描述:

给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。


岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。

此外,你可以假设该网格的四条边均被水包围。

97599fd56fbd4a9582975576cf07c28c.png


注意:


m == grid.length

n == grid[i].length

1 <= m, n <= 300

grid[i][j] 的值为 ‘0’ 或 ‘1’

解题思路:

创建一个队列,遍历数组如果遇到岛屿的话ret加一,并把岛屿的值改为’0’和它的下标入队列,如果队列不为空就取出队列的元素,搜索该位置的上下左右是否还有其他岛屿,有的话也同样把岛屿的值改为’0’和它的下标入队列,直到最后队列为空就算排除一座岛屿。


完整代码:

class Solution 
{
private:
    int around[4][2]=
    {
        {1, 0},
        {-1, 0},
        {0, 1},
        {0, -1}
    };
public:
    int numIslands(vector<vector<char>>& grid) 
    {
        int ret = 0;
        queue<pair<int, int>> q;
        int m = grid.size();
        int n = grid[0].size();
        // 1、遍历数组,遇到岛屿ret就加一
        for(int i=0; i<m; ++i)
        {
            for(int j=0; j<n; ++j)
            {
                // 2、遇到岛屿ret就加一,把该岛屿下标入队列,并把其值置为'0'
                if(grid[i][j] == '1')
                {
                    q.push(make_pair(i, j));
                    ++ret;
                    // 搜索入栈岛屿的上下左右位置是否还有其他岛屿,有的话也入队列,把值置为'0'
                    // 直到最后队列为空,就算排除了一座岛屿
                    while(!q.empty())
                    {
                        int row = q.front().first;
                        int col = q.front().second;
                        grid[row][col] = '0';
                        q.pop();
                        for(int k=0; k<4 ;++k)
                        {
                            int newRow = row+around[k][0];
                            int newCol = col+around[k][1];
                            if(newRow<0 || newRow>=m || newCol<0 || newCol>=n || grid[newRow][newCol] != '1')
                            {
                                continue;
                            }
                            grid[newRow][newCol] = '0';
                            q.push(make_pair(newRow, newCol));
                        }
                    }
                }
            }
        }
        return ret;
    }
};


性能分析:

时间复杂度:O(m*n)。

空间复杂度:O(min(m, n))。最坏情况全部是岛屿,因为一次可以排除该位置的上下左右,所以最多队列一次性存储元素的长度为min(m, n)。


5、钥匙和房间

题目连接


题目描述:

bca0ac5f782d4ad3b026773edca5bcec.png


解题思路:

最初,除 0 号房间外的其余所有房间都被锁住,我们从0号房间开始搜索,遍历每个房间中其他房间的钥匙,搜索过程中统计可以进入的房间数量并标记每个房间是否已经进入。


完整代码:

class Solution {
public:
    bool canVisitAllRooms(vector<vector<int>>& rooms) 
    {
        // 1、定义了三个对象
        // q:用来进行BFS的存储房间下标的队列
        // count:统计能进入房间的数量
        // vis:标记每个房间是否有被进入过
        queue<int> q;
        int count = 0;
        vector<bool> vis(rooms.size(), false);
        // 2、第0号房间先入队列进行BFS
        q.push(0);
        ++count;
        vis[0] = true;
        while(!q.empty())
        {
            int front = q.front();
            q.pop();
            // 遍历当前房间中其他房间的钥匙
            for(auto e : rooms[front])
            {
                if(!vis[e])
                {
                    q.push(e);
                    ++count;
                    vis[e] = true;
                }
            }
        }
        // 3、最终返回记录的count是否等于总的房间数
        return count == rooms.size();
    }
};


性能分析:


时间复杂度:O(n+m)。n为房间总数,m为所有房间中钥匙的总数。

空间复杂度:O(n)。队列和vis的开销。


6、机器人移动范围

题目连接


题目描述:

地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?


输入描述:

一行三个正整数由空格分开,分别代表行数m,列数n,和坐标数位之和的阈值k,0 < m <= 100, 0 < n <= 100, 0 < k <= 20。


输出描述:

一个正整数,代表该机器人能够到达的格子数量。


示例:

输入:

3 3 6

输出:

9


解题思路:

从(0, 0)开始向上下左右四个方向搜索,如果坐标合法、坐标数位之和不大于k,并且没有被访问过就把这个坐标入队列。


完整代码:


#include <queue>
#include <utility>
#include <vector>
#include <iostream>
using namespace std;
// 判断当前格子能否进入
bool IsEnter(int m, int n, int k)
{
  int sum = 0;
  while(m)
  {
    sum += m%10;
    m /= 10;
  }
  while(n)
  {
    sum += n%10;
    n /= 10;
  }
  return sum <= k;
}
int PathRange(vector<vector<bool>>& matrix, int k)
{
  // 1、定义相关对象
  // matrix:标记格子的访问情况
  // count:统计机器人能够达到多少个格子
  // q:进行BFS的存储坐标键值对的队列
  int count = 0;
  queue<pair<int, int>> q;
  // 2、从坐标0,0的格子开始搜索
  if(IsEnter(0, 0, k))
  {
    matrix[0][0] = true;
    ++count;
    q.push(make_pair(0, 0));
  }
  while(!q.empty())
  {
    pair<int, int> front = q.front();
    int m = front.first;
    int n = front.second;
    q.pop();
  // 上下左右四个方向搜索
    if(m-1>=0 && !matrix[m-1][n] && IsEnter(m-1, n, k))
    {
      matrix[m-1][n] = true;
      ++count;
      q.push(make_pair(m-1, n));
    }
    if(m+1<(int)matrix.size() && !matrix[m+1][n] && IsEnter(m+1, n, k))
    {
      matrix[m+1][n] = true;
      ++count;
      q.push(make_pair(m+1, n));
    }
    if(n-1>=0 && !matrix[m][n-1] && IsEnter(m, n-1, k))
    {
      matrix[m][n-1] = true;
      ++count;
      q.push(make_pair(m, n-1));
    }
    if(n+1<(int)matrix[0].size() && !matrix[m][n+1] && IsEnter(m, n+1, k))
    {
      matrix[m][n+1] = true;
      ++count;
      q.push(make_pair(m, n+1));
    }
  }
  // 3、count就是最终到达的格子数
  return count;
}
int main()
{
  int row = 0;
  int col = 0;
  int k = 0;
  cin>>row>>col>>k;
  vector<vector<bool>> matrix(row, vector<bool>(col, false));
  cout<<PathRange(matrix, k)<<endl;
  return 0;
}


性能分析:


时间复杂度:O(m*n)。最多每一个格子都能到达。

空间复杂度:O(m*n)。队列的开销。


相关文章
|
5月前
|
存储 程序员 C语言
c++primer plus 6 读书笔记 第四章 复合类型
c++primer plus 6 读书笔记 第四章 复合类型
|
1月前
|
存储 编译器 程序员
C++类型参数化
【10月更文挑战第1天】在 C++ 中,模板是实现类型参数化的主要工具,用于编写能处理多种数据类型的代码。模板分为函数模板和类模板。函数模板以 `template` 关键字定义,允许使用任意类型参数 `T`,并在调用时自动推导具体类型。类模板则定义泛型类,如动态数组,可在实例化时指定具体类型。模板还支持特化,为特定类型提供定制实现。模板在编译时实例化,需放置在头文件中以确保编译器可见。
33 11
|
2月前
|
安全 程序员 C语言
C++(四)类型强转
本文详细介绍了C++中的四种类型强制转换:`static_cast`、`reinterpret_cast`、`const_cast`和`dynamic_cast`。每种转换都有其特定用途和适用场景,如`static_cast`用于相关类型间的显式转换,`reinterpret_cast`用于低层内存布局操作,`const_cast`用于添加或移除`const`限定符,而`dynamic_cast`则用于运行时的类型检查和转换。通过具体示例展示了如何正确使用这四种转换操作符,帮助开发者更好地理解和掌握C++中的类型转换机制。
|
3月前
|
C++
使用 QML 类型系统注册 C++ 类型
使用 QML 类型系统注册 C++ 类型
58 0
|
4月前
|
编译器 C++ 运维
开发与运维函数问题之函数的返回类型如何解决
开发与运维函数问题之函数的返回类型如何解决
38 6
|
3月前
|
存储 C++
【C/C++学习笔记】string 类型的输入操作符和 getline 函数分别如何处理空白字符
【C/C++学习笔记】string 类型的输入操作符和 getline 函数分别如何处理空白字符
43 0
|
4月前
|
安全 编译器 C++
C++一分钟之-模板元编程实例:类型 traits
【7月更文挑战第15天】C++的模板元编程利用编译时计算提升性能,类型traits是其中的关键,用于查询和修改类型信息。文章探讨了如何使用和避免过度复杂化、误用模板特化及依赖特定编译器的问题。示例展示了`is_same`类型trait的实现,用于检查类型相等。通过`add_pointer`和`remove_reference`等traits,可以构建更复杂的类型转换逻辑。类型traits增强了代码效率和安全性,是深入C++编程的必备工具。
76 11
|
4月前
|
C++
C++一分钟之-类型别名与using声明
【7月更文挑战第20天】在C++中,类型别名和`using`声明提升代码清晰度与管理。类型别名简化复杂类型,如`using ComplexType = std::vector&lt;std::shared_ptr&lt;int&gt;&gt;;`,需注意命名清晰与适度使用。`using`声明引入命名空间成员,避免`using namespace std;`全局污染,宜局部与具体引入,如`using math::pi;`。恰当应用增强代码质量,规避常见陷阱。
78 5
|
3月前
|
设计模式 安全 IDE
C++从静态类型到单例模式
C++从静态类型到单例模式
38 0
|
4月前
|
C++ 开发者
C++一分钟之-概念(concepts):C++20的类型约束
【7月更文挑战第4天】C++20引入了Concepts,提升模板编程的类型约束和可读性。概念定义了模板参数需遵循的规则。常见问题包括过度约束、约束不完整和重载决议复杂性。避免问题的关键在于适度约束、全面覆盖约束条件和理解重载决议。示例展示了如何用Concepts限制模板函数接受的类型。概念将增强模板的安全性和灵活性,但需谨慎使用以防止错误。随着C++的发展,Concepts将成为必备工具。
94 2