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

前言

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

算法题

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

LeetCode 剑指 Offer 32 - III git

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

/* * 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 429web

给定一个 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 130svg

给定一个二维的矩阵,包含 ‘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网站查看,后续也将继续补充其余算法方面的相关题目,也会单独写一篇有关图的面试算法题文章。