广度优先搜索相关面试算法总结(非图论方面)

前言

此处总结的广度优先搜索相关面试算法题前三道主要侧重在树的各种“层序遍历”上,后面几道则侧重在对二维矩阵进行一些的操作,从而求得相应的值或矩阵,类似“被围绕的区域”,“最短的桥”,“腐烂的橘子”等类似题目。其几乎有统一的模板,文章后面会总结呈现给大家,重点在于领会广度优先搜索的算法思想。

算法题

1. LeetCode 剑指 Offer 32 - III : 从上到下打印二叉树 III

LeetCode 剑指 Offer 32 - III

请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。
例如:
给定二叉树: [3,9,20,null,null,15,7],
返回其层次遍历结果:
[
[3],
[20,9],
[15,7]
]
提示:
节点总数 <= 1000

/*
 * 1
 * LeetCode 剑指 Offer 32 - III : 从上到下打印二叉树 III
 * https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof/
 */
vector<vector<int>> levelOrder(TreeNode* root)
{
    vector<vector<int> > res;
    if(root == nullptr)
    {
        return res;
    }
    queue<TreeNode*> q;
    q.push(root);
    int i = 1;
    while(!q.empty())
    {
        int num = q.size();
        vector<int> tmp;
        for(int i = 0; i < num; i++)
        {
            TreeNode* node = q.front();
            q.pop();
            tmp.push_back(node->val);
            if(node->left != nullptr) {
                q.push(node->left);
            }
            if(node->right != nullptr) {
                q.push(node->right);
            }
        }
        if((i++%2) == 0)
        {
            reverse(tmp.begin(), tmp.end());
        }
        res.push_back(tmp);
    }
    return res;
}


2. LeetCode 429 : N叉树的层序遍历

LeetCode 429

给定一个 N 叉树,返回其节点值的层序遍历。 (即从左到右,逐层遍历)。
说明:
树的深度不会超过 1000。
树的节点总数不会超过 5000。

/*
 * 2
 * LeetCode 429 : N叉树的层序遍历
 * https://leetcode-cn.com/problems/n-ary-tree-level-order-traversal/
 */
vector<vector<int>> levelOrder(Node* root)
{
    vector<vector<int> > res;
    vector<int> tmp;
    if(root == NULL)
        return  res;
    queue<Node*> q;
    q.push(root);
    while(!q.empty())
    {
        tmp.clear();
        int count = q.size();
        while(count > 0)
        {
            Node* node = q.front();
            tmp.push_back(node->val);
            q.pop();
            count--;
            if(node->children.size() != 0)
                for(int i = 0; i < node->children.size(); i++)
                    q.push(node->children[i]);
        }
        res.push_back(tmp);
    }
    return res;
}


3. LeetCode 103. 二叉树的锯齿形层次遍历

LeetCode 103

给定一个二叉树,返回其节点值的锯齿形层次遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。

/*
 * 3
 * LeetCode 103. 二叉树的锯齿形层次遍历
 * https://leetcode-cn.com/problems/binary-tree-zigzag-level-order-traversal/
 */
vector<vector<int>> zigzagLevelOrder(TreeNode* root)
{
    vector<vector<int> > res;
    if(root == NULL)
        return res;
    queue<TreeNode*> q;
    q.push(root);
    int level = 0;
    while(!q.empty())
    {
        vector<int> tmp;
        int count = q.size();
        for(int i = 0; i < count; i++)
        {
            TreeNode *t=q.front();
            q.pop();
            if(level%2 == 0)
                tmp.push_back(t->val);
            else
                tmp.insert(tmp.begin(),t->val);
            if(t->left)
                q.push(t->left);
            if(t->right)
                q.push(t->right);
        }
        level++;
        res.push_back(tmp);
    }
    return res;
}


4. LeetCode 130 : 被围绕的区域

LeetCode 130

给定一个二维的矩阵,包含 ‘X’ 和 ‘O’(字母 O)。
找到所有被 ‘X’ 围绕的区域,并将这些区域里所有的 ‘O’ 用 ‘X’ 填充。
示例:
X X X X
X O O X
X X O X
X O X X
运行你的函数后,矩阵变为:
X X X X
X X X X
X X X X
X O X X
解释:
被围绕的区间不会存在于边界上,换句话说,任何边界上的 ‘O’ 都不会被填充为 ‘X’。 任何不在边界上,或不与边界上的 ‘O’ 相连的 ‘O’ 最终都会被填充为 ‘X’。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。

/*
 * 4
 * LeetCode 130 : 被围绕的区域
 * https://leetcode-cn.com/problems/surrounded-regions/
 */
const int dx[4] = {1, -1, 0, 0};
const int dy[4] = {0, 0, 1, -1};
void solve(vector<vector<char>>& board)
{
    int n = board.size();
    if(n == 0) {
        return;
    }
    int m = board[0].size();
    queue<pair<int, int>> que;
    for(int i = 0; i < n; i++) {
        if(board[i][0] == 'O') {
            que.emplace(i, 0);
        }
        if(board[i][m - 1] == 'O') {
            que.emplace(i, m - 1);
        }
    }
    for(int i = 1; i < m - 1; i++) {
        if(board[0][i] == 'O') {
            que.emplace(0, i);
        }
        if(board[n - 1][i] == 'O') {
            que.emplace(n - 1, i);
        }
    }
    while(!que.empty()) {
        int x = que.front().first, y = que.front().second;
        que.pop();
        board[x][y] = 'A';
        for(int i = 0; i < 4; i++) {
            int mx = x + dx[i], my = y + dy[i];
            if(mx < 0 || my < 0 || mx >= n || my >= m || board[mx][my] != 'O') {
                continue;
            }
            que.emplace(mx, my);
        }
    }
    for(int i = 0; i < n; i++) {
        for(int j = 0; j < m; j++) {
            if(board[i][j] == 'A') {
                board[i][j] = 'O';
            } else if(board[i][j] == 'O') {
                board[i][j] = 'X';
            }
        }
    }
}


5. LeetCode 934 : 最短的桥

LeetCode 934

在给定的二维二进制数组 A 中,存在两座岛。(岛是由四面相连的 1 形成的一个最大组。)
现在,我们可以将 0 变为 1,以使两座岛连接起来,变成一座岛。
返回必须翻转的 0 的最小数目。(可以保证答案至少是 1。)
示例 1:
输入:[[0,1],[1,0]]
输出:1
示例 2:
输入:[[0,1,0],[0,0,0],[0,0,1]]
输出:2
示例 3:
输入:[[1,1,1,1,1],[1,0,0,0,1],[1,0,1,0,1],[1,0,0,0,1],[1,1,1,1,1]]
输出:1
提示:
1 <= A.length = A[0].length <= 100
A[i][j] == 0 或 A[i][j] == 1

Method 1 :DFS + BFS

  1. 找到第一个1的位置。
  2. 使用 DFS 找出所有相连的1, 并将所有相连的1都放入到一个队列 queue 中,并且将该点的值改为2。
  3. 使用 BFS 进行层序遍历,每遍历一层,结果 res 都增加1,当遇到1时,直接返回 res 即可。
/*
 * 5
 * LeetCode 934 : 最短的桥
 * https://leetcode-cn.com/problems/shortest-bridge/
 */
/* Method 1 : DFS + BFS
1. 找到第一个1的位置。
2. 使用 DFS 找出所有相连的1, 并将所有相连的1都放入到一个队列 queue 中,并且将该点的值改为2。
3. 使用 BFS 进行层序遍历,每遍历一层,结果 res 都增加1,当遇到1时,直接返回 res 即可。
*/
void dfs(vector<vector<int> >& A, int x, int y, queue<int>& q)
{
    int n = A.size();
    if(x < 0 || x >= n || y < 0 || y >= n || A[x][y] == 0 || A[x][y] == 2)
        return;
    A[x][y] = 2;
    q.push(x * n + y);
    dfs(A, x + 1, y, q);
    dfs(A, x, y + 1, q);
    dfs(A, x - 1, y, q);
    dfs(A, x, y - 1, q);
}
int shortestBridge(vector<vector<int> >& A)
{
    int res = 0, n = A.size(), startX = -1, startY = -1;
    queue<int> q;
    vector<vector<int> > dirs = {{-1,0},{0,1},{1,0},{0,-1}};
    for(int i = 0; i < n; i++)
    {
        for(int j = 0; j < n; j++)
        {
            if(A[i][j] == 0)
                continue;
            startX = i; startY = j;
            break;
        }
        if(startX != -1)
            break;
    }
    dfs(A, startX, startY, q);
    while(!q.empty())
    {
        for(int i = q.size(); i > 0; i--)
        {
            int t = q.front();
            q.pop();
            for(int k = 0; k < dirs.size(); k++)
            {
                int x = t / n + dirs[k][0];
                int y = t % n + dirs[k][1];
                if(x < 0 || x >= n || y < 0 || y >= n || A[x][y] == 2)
                    continue;
                if(A[x][y] == 1)
                    return res;
                A[x][y] = 2;
                q.push(x * n + y);
            }
        }
        ++res;
    }
    return res;
}

Method 2:BFS + BFS

  1. 找到第一个1的位置。
  2. 使用 BFS 找出所有相连的1, 并将所有相连的1都放入到一个队列 queue 中,并且将该点的值改为2。
  3. 使用 BFS 进行层序遍历,每遍历一层,结果 res 都增加1,当遇到1时,直接返回 res 即可。
/* Method 2
1. 找到第一个1的位置。
2. 使用 BFS 找出所有相连的1, 并将所有相连的1都放入到一个队列 queue 中,并且将该点的值改为2。
3. 使用 BFS 进行层序遍历,每遍历一层,结果 res 都增加1,当遇到1时,直接返回 res 即可。

**注意 :第一个 BFS 不需要是层序遍历的,而第二个 BFS 是必须层序遍历。
*/
int shortestBridge(vector<vector<int>>& A)
{
    int res = 0, n = A.size();
    queue<int> que; // first BFS used
    queue<int> q;   // secod=nd BFS used

    vector<vector<int> > dirs = {{-1,0},{0,1},{1,0},{0,-1}};
    for(int i = 0; i < n; i++)
    {
        for(int j = 0; j < n; j++)
        {
            if(A[i][j] == 0)
                continue;
            A[i][j] = 2;
            que.push(i * n + j);
            break;
        }
        if(!que.empty())
            break;
    }
    // first BFS
    while(!que.empty())
    {
        int t = que.front();
        que.pop();
        q.push(t);
        for(int k = 0; k < 4; k++)
        {
            int x = t / n + dirs[k][0];
            int y = t % n + dirs[k][1];
            if(x < 0 || x >= n || y < 0 || y >= n || A[x][y] == 0 || A[x][y] == 2)
                continue;
            A[x][y] = 2;
            que.push(x * n + y);
        }
    }
    // second BFS
    while(!q.empty())
    {
        for(int i = q.size(); i > 0; i--)
        {
            int t = q.front();
            q.pop();
            for(int k = 0; k < 4; k++)
            {
                int x = t / n + dirs[k][0];
                int y = t % n + dirs[k][1];
                if(x < 0 || x >= n || y < 0 || y >= n || A[x][y] == 2)
                    continue;
                if(A[x][y] == 1)
                    return res;
                A[x][y] = 2;
                q.push(x * n + y);
            }
        }
        ++res;
    }
    return res;
}


6. LeetCode 994 : 腐烂的橘子

LeetCode 994

在给定的网格中,每个单元格可以有以下三个值之一:
值 0 代表空单元格;
值 1 代表新鲜橘子;
值 2 代表腐烂的橘子。
每分钟,任何与腐烂的橘子(在 4 个正方向上)相邻的新鲜橘子都会腐烂。
返回直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1。
示例 1:
输入:[[2,1,1],[1,1,0],[0,1,1]]
输出:4
示例 2:
输入:[[2,1,1],[0,1,1],[1,0,1]]
输出:-1
解释:左下角的橘子(第 2 行, 第 0 列)永远不会腐烂,因为腐烂只会发生在 4 个正向上。
示例 3:
输入:[[0,2]]
输出:0
解释:因为 0 分钟时已经没有新鲜橘子了,所以答案就是 0 。
提示:
1 <= grid.length <= 10
1 <= grid[0].length <= 10
grid[i][j] 仅为 0、1 或 2

/*
 * 6
 * LeetCode 994 : 腐烂的橘子
 * https://leetcode-cn.com/problems/rotting-oranges/
 */
int orangesRotting(vector<vector<int> >& grid)
{
    int ct = 0, res = -1;
    queue<vector<int> > q;
    vector<vector<int> > dir = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
    for(int i = 0; i < grid.size(); i++)
    {
        for(int j = 0; j < grid[i].size(); j++)
        {
            if(grid[i][j] > 0)
                ct++;
            if(grid[i][j] == 2)
                q.push({i, j});
        }
    }
    while(!q.empty())
    {
        res++;
        int n = q.size();
        for(int k = 0; k < n; k++)
        {
            vector<int> cur = q.front();
            ct--;
            q.pop();
            for(int i = 0; i < dir.size(); i++)
            {
                int x = cur[0] + dir[i][0];
                int y = cur[1] + dir[i][1];
                if(x >= grid.size() || x < 0 || y >= grid[0].size() || y < 0 || grid[x][y] != 1)
                    continue;
                grid[x][y] = 2;
                q.push({x, y});
            }
        }
    }
    if(ct == 0)
        return max(0, res);
    return -1;
}


7. LeetCode 542 : 01 矩阵

LeetCode 542

给定一个由 0 和 1 组成的矩阵,找出每个元素到最近的 0 的距离。
两个相邻元素间的距离为 1 。
示例 1:
输入:
0 0 0
0 1 0
0 0 0
输出:
0 0 0
0 1 0
0 0 0
示例 2:
输入:
0 0 0
0 1 0
1 1 1
输出:
0 0 0
0 1 0
1 2 1
注意:
给定矩阵的元素个数不超过 10000。
给定矩阵中至少有一个元素是 0。
矩阵中的元素只在四个方向上相邻: 上、下、左、右。

/*
 * 7
 * LeetCode 542 : 01 矩阵
 * https://leetcode-cn.com/problems/01-matrix/
 */
/*
BFS:
从每一个0位置向上下左右四个方向进行广播,如果广播到的位置距离0的距离大于当前位置距离0的距离加一,
则将广播到的位置距离0的位置更新为当前位置距离0的距离加一。
*/
vector<vector<int>> updateMatrix(vector<vector<int>>& matrix)
{
    int R = matrix.size();
    if(R == 0)
        return matrix;
    int C = matrix[0].size();
    vector<vector<int> > dist(R, vector<int>(C, INT_MAX));
    queue<pair<int,int> > q;
    for(int i = 0; i < R; i++)
    {
        for(int j = 0; j < C; j++)
        {
            if(matrix[i][j] == 0)
            {
                dist[i][j] = 0;
                q.push({i, j});
            }
        }
    }
    vector<vector<int> > dir = {{-1,0},{0,1},{1,0},{0,-1}};
    while(!q.empty())
    {
        pair<int, int> curr = q.front();
        q.pop();
        for(int i = 0; i < 4; i++)
        {
            int new_r = curr.first + dir[i][0];
            int new_c = curr.second + dir[i][1];
            if(new_r >= 0 && new_c >= 0 && new_r < R && new_c < C)
            {
                if(dist[new_r][new_c] > dist[curr.first][curr.second] + 1)
                {
                    dist[new_r][new_c] = dist[curr.first][curr.second] + 1;
                    q.push({ new_r, new_c });
                }
            }
        }
    }
    return dist;
}


8. LeetCode 1293 : 网格中的最短路径(Hard)

LeetCode 1293

给你一个 m * n 的网格,其中每个单元格不是 0(空)就是 1(障碍物)。每一步,您都可以在空白单元格中上、下、左、右移动。
如果您 最多 可以消除 k 个障碍物,请找出从左上角 (0, 0) 到右下角 (m-1, n-1) 的最短路径,并返回通过该路径所需的步数。如果找不到这样的路径,则返回 -1。
示例 1:
输入:
grid =
[[0,0,0],
[1,1,0],
[0,0,0],
[0,1,1],
[0,0,0]],
k = 1
输出:6
解释:
不消除任何障碍的最短路径是 10。
消除位置 (3,2) 处的障碍后,最短路径是 6 。该路径是 (0,0) -> (0,1) -> (0,2) -> (1,2) -> (2,2) -> (3,2) -> (4,2).
示例 2:
输入:
grid =
[[0,1,1],
[1,1,1],
[1,0,0]],
k = 1
输出:-1
解释:
我们至少需要消除两个障碍才能找到这样的路径。
提示:
grid.length == m
grid[0].length == n
1 <= m, n <= 40
1 <= k <= m*n
grid[i][j] == 0 or 1
grid[0][0] == grid[m-1][n-1] == 0

/*
 * Hard
 * LeetCode 1293 : 网格中的最短路径
 * https://leetcode-cn.com/problems/shortest-path-in-a-grid-with-obstacles-elimination/
 */
struct Nagato
{
    int x, y;
    int rest;
    Nagato(int _x, int _y, int _r): x(_x), y(_y), rest(_r) {}
};

class Solution
{
public:
    vector<vector<int> > dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
    int shortestPath(vector<vector<int>>& grid, int k)
    {
        if(grid.empty() || grid[0].empty())
        {
            return 0;
        }
        int m = grid.size(), n = grid[0].size();
        if(m == 1 && n == 1)
        {
            return 0;
        }
        k = min(k, m + n - 3);
        vector<vector<vector<bool> > > vis(m, vector<vector<bool> >(n, vector<bool>(k+1, false)));
        queue<Nagato> q;
        q.push(Nagato(0, 0, k));
        vis[0][0][k] = true;
        for(int step = 1; q.size() > 0; ++step)
        {
            int cnt = q.size();
            for(int j = 0; j < cnt; ++j)
            {
                Nagato cur = q.front();
                q.pop();
                for(int i = 0; i < dirs.size(); ++i)
                {
                    int nx = cur.x + dirs[i][0];
                    int ny = cur.y + dirs[i][1];
                    if(nx >= 0 && nx < m && ny >= 0 && ny < n)
                    {
                        if(grid[nx][ny] == 0 && !vis[nx][ny][cur.rest]) {
                            if(nx == m - 1 && ny == n - 1) {
                                return step;
                            }
                            q.push(Nagato(nx, ny, cur.rest));
                            vis[nx][ny][cur.rest] = true;
                        } else if(grid[nx][ny] == 1 && cur.rest > 0 && !vis[nx][ny][cur.rest - 1]) {
                            q.push(Nagato(nx, ny, cur.rest - 1));
                            vis[nx][ny][cur.rest - 1] = true;
                        }
                    }
                }
            }
        }
        return -1;
    }
};


注:最后一题“LeetCode 1293 : 网格中的最短路径”,难度比较大,答案代码摘抄官方解答

模版

/* 
 *模版
 */
queue<T> q;
while(!q.empty())
{
    ...//
    int n = q.size();
    for(int k = 0; k < n; k++)
    {
        T t = q.front();
        ...//
        q.pop();
        for(扩展方式)
        {
            if(扩展方式所达到状态合法)
            {
                ....//根据题意来添加
                q.push()}
        }
    }
}


总结

以上是广度优先搜索相关面试算法题汇总,个别题目也给出了解题思路和注释。
所有代码都可以去我的GitHub网站查看,后续也将继续补充其他算法方面的相关题目,也会单独写一篇有关图的面试算法题文章。

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页