广度优先遍历和深度优先遍历的共同应用

0.1 广度优先遍历

0.1.1 定义

广度优先遍历是连通图的一种遍历策略。因为它的思想是从一个顶点V0开始,辐射状地优先遍历其周围相邻的的区域。(处理最短路径问题

0.1.1 代码实现方式

for循环或while循环实现,并利用队列数据结构存储上一层遍历的结果

0.2 深度优先遍历的定义

0.2.1 定义及思路

首先以一个未被访问过的顶点作为起始顶点,沿当前顶点的边走到未访问过的顶点;当没有未访问过的顶点时,则回到上一个顶点,继续试探访问别的顶点,直到所有的顶点都被访问。(处理最长路径问题

0.2.2 代码实现方式

递归函数实现,利用编译器调用的stack数据结构存储每一次递归的数据,返回时就能找到递归之前的变量状态

0.3 看别人写的代码的快速方法

如果不太好理解别人写的代码,则最好拿例子一步一步地跟着代码走,这样就知道代码的每一步做了什么事情了

1. Surrounded Regions

题目描述

给定一个包含'X'和'O'(字母 O)的2D矩阵,检索所有被包围的区域'X'。

通过在所围绕的区域中将所有'O's 翻转成'X's 来捕获区域。

例如,矩阵形式如下:

XXXX
XOOX
XXOX
XOXX

运行你的函数后,该矩阵应该是:

XXXX
XXXX
XXXX
XOXX

代码实现(广度优先遍历)

public class Solution {
    public void solve(char[][] board) {
        if (board.length == 0) return;
        
        int rowN = board.length;
        int colN = board[0].length;
        Queue<Point> queue = new LinkedList<Point>();
       
       //get all 'O's on the edge first
        for (int r = 0; r< rowN; r++){
            if (board[r][0] == 'O') {
                board[r][0] = '+';
                queue.add(new Point(r, 0));
            }
            if (board[r][colN-1] == 'O') {
                board[r][colN-1] = '+';
                queue.add(new Point(r, colN-1));
            }
            }
        
        for (int c = 0; c< colN; c++){
            if (board[0][c] == 'O') {
                board[0][c] = '+';
                queue.add(new Point(0, c));
            }
            if (board[rowN-1][c] == 'O') {
                board[rowN-1][c] = '+';
                queue.add(new Point(rowN-1, c));
            }
            }
        
        //BFS for the 'O's, and mark it as '+'
        while (!queue.isEmpty()){
            Point p = queue.poll();
            int row = p.x;
            int col = p.y;
            if (row - 1 >= 0 && board[row-1][col] == 'O') {board[row-1][col] = '+'; queue.add(new Point(row-1, col));}
            if (row + 1 < rowN && board[row+1][col] == 'O') {board[row+1][col] = '+'; queue.add(new Point(row+1, col));}
            if (col - 1 >= 0 && board[row][col - 1] == 'O') {board[row][col-1] = '+'; queue.add(new Point(row, col-1));}
            if (col + 1 < colN && board[row][col + 1] == 'O') {board[row][col+1] = '+'; queue.add(new Point(row, col+1));}                
        }
        
        //turn all '+' to 'O', and 'O' to 'X'
        for (int i = 0; i<rowN; i++){
            for (int j=0; j<colN; j++){
                if (board[i][j] == 'O') board[i][j] = 'X';
                if (board[i][j] == '+') board[i][j] = 'O';
            }
        }        
    }
}

代码实现(深度优先遍历)

class Solution {
    public void solve(char[][] board) {
        if (board.length == 0 || board[0].length == 0) return;
        if (board.length < 3 || board[0].length < 3) return;
        int m = board.length;
        int n = board[0].length;
        for (int i = 0; i < m; i++) {
            if (board[i][0] == 'O') helper(board, i, 0);
            if (board[i][n - 1] == 'O') helper(board, i, n - 1);
        }
        for (int j = 1; j < n - 1; j++) {
            if (board[0][j] == 'O') helper(board, 0, j);
            if (board[m - 1][j] == 'O') helper(board, m - 1, j);
        }
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (board[i][j] == 'O') board[i][j] = 'X';
                if (board[i][j] == '*') board[i][j] = 'O';
            }
        }
    }
    
    private void helper(char[][] board, int r, int c) {
        if (r < 0 || c < 0 || r > board.length - 1 || c > board[0].length - 1 || board[r][c] != 'O') return;
        board[r][c] = '*';
        helper(board, r + 1, c);
        helper(board, r - 1, c);
        helper(board, r, c + 1);
        helper(board, r, c - 1);
    }
}

2.Number of Islands

题目描述

给定'1's(陆地)和'0's(水)的二维网格图,计算岛的数量。一个岛被水包围,并且通过水平或垂直连接相邻的陆地而形成。你可以假设网格的所有四个边都被水包围。

例1:

11110
11010
11000
00000

答:1

例2:

11000
11000
00100
00011

答:3

代码实现(广度优先遍历)

public int numIslands(char[][] grid) {
    int count=0;
    for(int i=0;i<grid.length;i++)
        for(int j=0;j<grid[0].length;j++){
            if(grid[i][j]=='1'){
                bfsFill(grid,i,j);
                count++;
            }
        }
    return count;
}
private void bfsFill(char[][] grid,int x, int y){
    grid[x][y]='0';
    int n = grid.length;
    int m = grid[0].length;
    LinkedList<Integer> queue = new LinkedList<Integer>();  
    int code = x*m+y;  
    queue.offer(code);  
    while(!queue.isEmpty())  
    {  
        code = queue.poll();  
        int i = code/m;  
        int j = code%m;  
        if(i>0 && grid[i-1][j]=='1')    //search upward and mark adjacent '1's as '0'.
        {  
            queue.offer((i-1)*m+j);  
            grid[i-1][j]='0';  
        }  
        if(i<n-1 && grid[i+1][j]=='1')  //down
        {  
            queue.offer((i+1)*m+j);  
            grid[i+1][j]='0';  
        }  
        if(j>0 && grid[i][j-1]=='1')  //left
        {  
            queue.offer(i*m+j-1);  
            grid[i][j-1]='0';  
        }  
        if(j<m-1 && grid[i][j+1]=='1')  //right
        {  
            queue.offer(i*m+j+1);  
            grid[i][j+1]='0';  
        }
    } 
}

代码实现(深度优先遍历)


private int n;
private int m;

public int numIslands(char[][] grid) {
    int count = 0;
    n = grid.length;
    if (n == 0) return 0;
    m = grid[0].length;
    for (int i = 0; i < n; i++){
        for (int j = 0; j < m; j++)
            if (grid[i][j] == '1') {
                DFSMarking(grid, i, j);
                ++count;
            }
    }    
    return count;
}

private void DFSMarking(char[][] grid, int i, int j) {
    if (i < 0 || j < 0 || i >= n || j >= m || grid[i][j] != '1') return;
    grid[i][j] = '0';
    DFSMarking(grid, i + 1, j);
    DFSMarking(grid, i - 1, j);
    DFSMarking(grid, i, j + 1);
    DFSMarking(grid, i, j - 1);
}

3.Minesweeper

给你一个代表游戏板的2D字符矩阵。'M'代表一个未揭露的地雷,'E'代表一个未被揭露的空方块,'B'代表一个显露的空白方块,不存在相邻的(上,下,左,右和所有4个对角线)地雷,数字('1'到'8')表示有多少个地雷与这个暴露的正方形相邻,最后是'X'代表一个透露的地雷。

现在给出下一个点击位置(行和列索引)在所有未被揭示的方块('M'或'E')之间,按照以下规则揭示该位置后返回棋盘:

如果发现一个地雷('M'),那么游戏结束 - 将其更改为'X'。
如果显示没有相邻地雷的空方块('E'),则将其更改为显示空白('B'),并且所有相邻的未发现方块应递归显示。
如果显示至少有一个相邻地雷的空方块('E'),则将其更改为代表相邻地雷数量的数字('1'至'8')。
当没有更多的方块会显示时,请返回棋盘。
例1:
输入
[['E','E','E','E','E'],
['E','E','M','E','E'],
['E','E','E','E','E'],
['E','E','E','E','E']]

点击:[3,0]

输出:
[['B','1','E','1','B'],
['B','1','M','1','B'],
['B','1','1','1','B' ],
['B','B','B','B','B']

说明:

例2:

输入:
[['B','1','E','1','B'],
['B','1','M','1','B'],
['B','1','1','1','B' ],
['B','B','B','B','B']

点击:[1,2]

输出:
[['B','1','E','1','B'],
['B','1','X','1','B' ],
['B','1','1','1','B' ],
['B','B','B','B','B']

说明:

思路分析

这是一个典型的Search问题,无论是使用DFS或BFS。搜索规则:

1.如果点击地雷(' M'),将其标记为' X',停止进一步搜索。
2.如果点击空单元格(' E'),取决于周围的矿井数量:
2.1有周围的地雷,用周围地雷的数量标记,停止进一步的搜索。
2.2没有周围的地雷,将其标记为“ B',继续搜索它的8个邻居。

代码实现(广度优先遍历)

public class Solution {
    public char[][] updateBoard(char[][] board, int[] click) {
        int m = board.length, n = board[0].length;
        Queue<int[]> queue = new LinkedList<>();
        queue.add(click);
        
        while (!queue.isEmpty()) {
            int[] cell = queue.poll();
            int row = cell[0], col = cell[1];
            
            if (board[row][col] == 'M') { // Mine
                board[row][col] = 'X';
            }
            else { // Empty
                // Get number of mines first.
                int count = 0;
                // 这里的两层for循环目的是为了检索这个点的上下左右和四个角,其实这个与前面的四个递归本质是一样的,只不过这里要8个,所以用for循环更好,而且本来用递归效率就不高。
                for (int i = -1; i < 2; i++) {
                    for (int j = -1; j < 2; j++) {
                        if (i == 0 && j == 0) continue;
                        int r = row + i, c = col + j;
                        if (r < 0 || r >= m || c < 0 || c < 0 || c >= n) continue;
                        if (board[r][c] == 'M' || board[r][c] == 'X') count++;
                    }
                }
                
                if (count > 0) { // If it is not a 'B', stop further BFS.
                    board[row][col] = (char)(count + '0');
                }
                else { // Continue BFS to adjacent cells.
                    board[row][col] = 'B';
                    for (int i = -1; i < 2; i++) {
                        for (int j = -1; j < 2; j++) {
                            if (i == 0 && j == 0) continue;
                            int r = row + i, c = col + j;
                            if (r < 0 || r >= m || c < 0 || c < 0 || c >= n) continue;
                            if (board[r][c] == 'E') {
                                queue.add(new int[] {r, c});
                                board[r][c] = 'B'; // Avoid to be added again.
                            }
                        }
                    }
                }
            }
        }
        
        return board;
    }
}

代码实现(深度优先遍历)

public class Solution {
    public char[][] updateBoard(char[][] board, int[] click) {
        int m = board.length, n = board[0].length;
        int row = click[0], col = click[1];
        
        if (board[row][col] == 'M') { // Mine
            board[row][col] = 'X';
        }
        else { // Empty
            // Get number of mines first.
            int count = 0;
            for (int i = -1; i < 2; i++) {
                for (int j = -1; j < 2; j++) {
                    if (i == 0 && j == 0) continue;
                    int r = row + i, c = col + j;
                    if (r < 0 || r >= m || c < 0 || c < 0 || c >= n) continue;
                    if (board[r][c] == 'M' || board[r][c] == 'X') count++;
                }
            }
            
            if (count > 0) { // If it is not a 'B', stop further DFS.
                board[row][col] = (char)(count + '0');
            }
            else { // Continue DFS to adjacent cells.
                board[row][col] = 'B';
                for (int i = -1; i < 2; i++) {
                    for (int j = -1; j < 2; j++) {
                        if (i == 0 && j == 0) continue;
                        int r = row + i, c = col + j;
                        if (r < 0 || r >= m || c < 0 || c < 0 || c >= n) continue;
                        if (board[r][c] == 'E') updateBoard(board, new int[] {r, c}); //递归实现数据传递和深度优先遍历,这是唯一与广度优先遍历的区别
                    }
                }
            }
        }
        
        return board;
    }
}

4. 0&1 Matrix

问题描述

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

注意:
给定矩阵的元素数量不会超过10,000。
给定矩阵中至少有一个0。
细胞只在四个方向上相邻:向上,向下,向左和向右。

代码实现(广度优先遍历)

public class Solution {
    public int[][] updateMatrix(int[][] matrix) {
        int m = matrix.length;
        int n = matrix[0].length;
        
        Queue<int[]> queue = new LinkedList<>();
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (matrix[i][j] == 0) {
                    queue.offer(new int[] {i, j});
                }
                else {
                    matrix[i][j] = Integer.MAX_VALUE; //非0的矩阵值设为整型最大值
                }
            }
        }
        //此矩阵的目的就是实现寻找某个点的上下左右四个相邻点
        int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
        
        while (!queue.isEmpty()) {
            int[] cell = queue.poll();
            for (int[] d : dirs) {
                int r = cell[0] + d[0];
                int c = cell[1] + d[1];
                if (r < 0 || r >= m || c < 0 || c >= n || 
                    matrix[r][c] <= matrix[cell[0]][cell[1]] + 1) continue;  //如果原来是1,则矩阵的值为整型最大值,没有谁会大于本点,如果本点的上下左右四个点都小于等于本点,表示本点的周围没有1
                queue.add(new int[] {r, c});
                matrix[r][c] = matrix[cell[0]][cell[1]] + 1;  //如果遇到某个点的左右上下有大于本点的,表示遇到了本点相邻为1的情况,此时整型最大值加1就是1,因为溢出了
            }
        }
        
        return matrix;
    }
}

代码实现(深度优先遍历)

public class Solution {
    public int[][] updateMatrix(int[][] matrix) {
        if(matrix.length==0) return matrix;
        
        for(int i = 0; i<matrix.length; i++)
            for(int j = 0; j<matrix[0].length; j++)
                if(matrix[i][j]==1&&!hasNeiberZero(i, j,matrix)) 
                    matrix[i][j] = matrix.length+matrix[0].length+1;
        
        for(int i = 0; i<matrix.length; i++)
            for(int j = 0; j<matrix[0].length; j++)
                if(matrix[i][j]==1)
                    dfs(matrix, i, j, -1);
        
        return matrix;
    }
    private void dfs(int[][] matrix, int x, int y, int val){
        if(x<0||y<0||y>=matrix[0].length||x>=matrix.length||matrix[x][y]<=val)
            return;
        
        if(val>0) matrix[x][y] = val;
        
        dfs(matrix, x+1, y, matrix[x][y]+1);
        dfs(matrix, x-1, y, matrix[x][y]+1);
        dfs(matrix, x, y+1, matrix[x][y]+1);
        dfs(matrix, x, y-1, matrix[x][y]+1);
        
    }
    private boolean hasNeiberZero(int x, int y, int[][] matrix){
        if(x>0&&matrix[x-1][y]==0) return true;
        if(x<matrix.length-1&&matrix[x+1][y]==0) return true;
        if(y>0&&matrix[x][y-1]==0) return true;
        if(y<matrix[0].length-1&&matrix[x][y+1]==0) return true;
        
        return false;
    }
}

5.Minimum Depth of Binary Tree

题目描述

给定一个二叉树,找到它的最小深度。

最小深度是沿着从根节点到最近叶节点的最短路径的节点数量。

代码实现(广度优先遍历)

public int minDepth(TreeNode root) {
    if(root == null) return 0;
    int depth = 1;
    Queue<TreeNode> q = new LinkedList<TreeNode>();
    q.offer(root);
    while(!q.isEmpty()){
        int size = q.size();
        // for each level
        for(int i=0;i<size;i++){
            TreeNode node = q.poll();
            if(node.left == null && node.right == null){
                return depth;
            }
            if(node.left != null){
                q.offer(node.left);
            }
            if(node.right != null){
                q.offer(node.right);
            }
        }
        depth++;
    }
    return depth;
}

代码实现(深度优先遍历)

public class Solution {
    public int minDepth(TreeNode root) {
        if(root == null) return 0;
        int left = minDepth(root.left);
        int right = minDepth(root.right);
        return (left == 0 || right == 0) ? left + right + 1: Math.min(left,right) + 1;
       
    }
}
###复杂度
BFS和DFS的复杂度不一样,BFS要好些,所以虽然都可以完成功能,但是因为算法效率不一样,最后复杂度也不一样。

6.Course Schedule II

问题描述

总共有n个课程需要学习,标记为0 至 n - 1。

有些课程可能有先决条件,例如,如果选择课程0,则必须先参加课程1,该课程表示为一对: [0,1]

输入课程总数和先修课程的条件对列表,请返回应完成所有课程的顺序。

可能有多个正确的命令,你只需要返回其中的一个。如果不可能完成所有课程,则返回一个空数组。

例如:

2, [[1,0]]

总共有2门课程可供选择。要参加课程1,你应该已经完成​​课程0.所以正确的课程顺序是[0,1]

4, [[1,0],[2,0],[3,1],[3,2]]

总共有4门课程可供选择。要参加课程3,你应该已经完成​​了课程1和2.在完成课程0之后,应该采用课程1和2。因此,一个正确的课程顺序是[0,1,2,3]。另一个正确的顺序是[0,2,1,3]

代码实现(广度优先遍历)

1.邻接表图结构

private int[] solveByBFS(int[] incLinkCounts, List<List<Integer>> adjs){
    int[] order = new int[incLinkCounts.length];
    Queue<Integer> toVisit = new ArrayDeque<>();
    for (int i = 0; i < incLinkCounts.length; i++) {
        if (incLinkCounts[i] == 0) toVisit.offer(i);
    }
    int visited = 0;
    while (!toVisit.isEmpty()) {
        int from = toVisit.poll();
        order[visited++] = from;
        for (int to : adjs.get(from)) {
            incLinkCounts[to]--;
            if (incLinkCounts[to] == 0) toVisit.offer(to);
        }
    }
    return visited == incLinkCounts.length ? order : new int[0]; 
}

2.邻接矩阵图结构

public int[] findOrder(int numCourses, int[][] prerequisites) { 
    if (numCourses == 0) return null; //如果修的课程为0,则直接返回-1
    // Convert graph presentation from edges to indegree of adjacent list.
    int indegree[] = new int[numCourses], order[] = new int[numCourses], index = 0;
    //order数组表示输出修的课程的顺序,indegree数组表示每一门课程前面需要修的其他课程的总数
    for (int i = 0; i < prerequisites.length; i++) // Indegree - how many prerequisites are needed.
        indegree[prerequisites[i][0]]++;    //计算每门课需要需要先修的课数,即前面有几门课需要先修

    Queue<Integer> queue = new LinkedList<Integer>();
    for (int i = 0; i < numCourses; i++) 
        if (indegree[i] == 0) {  //把不需要先修的课程放入到队列中,没有顺序,仅仅根据课程编号的顺序执行
            // Add the course to the order because it has no prerequisites.
            order[index++] = i;
            queue.offer(i);
        }

    // How many courses don't need prerequisites. 
    while (!queue.isEmpty()) {
        int prerequisite = queue.poll(); // Already finished this prerequisite course.
        for (int i = 0; i < prerequisites.length; i++)  {
            if (prerequisites[i][1] == prerequisite) {
                indegree[prerequisites[i][0]]--; //取出一个先修课程,然后遍历找出在此课程之前需要修的数量,减去1
                if (indegree[prerequisites[i][0]] == 0) { //如果先修课程为0了,此时加入到队列中,作为其他的课程的先修课程
                    // If indegree is zero, then add the course to the order.
                    order[index++] = prerequisites[i][0]; //更新先修课程的输出顺序
                    queue.offer(prerequisites[i][0]); 
                }
            } 
        }
    }

    return (index == numCourses) ? order : new int[0];
}

代码实现(深度优先遍历)

private int[] solveByDFS(List<List<Integer>> adjs) {
    BitSet hasCycle = new BitSet(1);
    BitSet visited = new BitSet(adjs.size());
    BitSet onStack = new BitSet(adjs.size());
    Deque<Integer> order = new ArrayDeque<>();
    for (int i = adjs.size() - 1; i >= 0; i--) {
        if (visited.get(i) == false && hasOrder(i, adjs, visited, onStack, order) == false) return new int[0];
    }
    int[] orderArray = new int[adjs.size()];
    for (int i = 0; !order.isEmpty(); i++) orderArray[i] = order.pop();
    return orderArray;
}

private boolean hasOrder(int from, List<List<Integer>> adjs, BitSet visited, BitSet onStack, Deque<Integer> order) {
    visited.set(from);
    onStack.set(from);
    for (int to : adjs.get(from)) {
        if (visited.get(to) == false) {
            if (hasOrder(to, adjs, visited, onStack, order) == false) return false;
        } else if (onStack.get(to) == true) {
            return false;
        }
    }
    onStack.clear(from);
    order.push(from);
    return true;
}

7.Cheapest Flights Within K Stops

题目描述

有m个航班连接n个城市。航班从城市u飞抵城市v,机票价格为w。

输入所有的城市和机票价格,出发城市为src和目的地为dst,任务是找到从城市src到dst中间停留最多k站的最便宜的机票价格。如果没有这样的路线,输出-1。

Example 1:
Input: 
n = 3, edges = [[0,1,100],[1,2,100],[0,2,500]]
src = 0, dst = 2, k = 1
Output: 200
Explanation: 
The graph looks like this:


The cheapest price from city 0 to city 2 with at most 1 stop costs 200, as marked red in the picture.

题目分析

本题与上一题本质是一样的,就是从一个地方走到另外一个地方,之间有对应关系。直接表现为广度优先遍历或深度优先遍历的情况

代码实现(BFS)

public int findCheapestPrice(int n, int[][] flights, int src, int dst, int k) {
        //使用map数据结构的目的是为了集合可能的多个从同一个城市(顶点)出发的数据,同时为了避免混淆同一个城市的数据,用目标城市来作为value中的map的key,price值作为目标城市的key,利用这种数据结构可以实现唯一性
        Map<Integer, Map<Integer, Integer>> prices = new HashMap<>();
        //创建图结构,利用map实现顶点f[0](城市名)到目标城市f[1]与价格f[2]的映射
        for (int[] f : flights) {
            if (!prices.containsKey(f[0])) prices.put(f[0], new HashMap<>());
            prices.get(f[0]).put(f[1], f[2]);
        }
        //优先队列实现那个价格更小,然后放到前面,这样就可以实现找到结果就可以终止程序
        Queue<int[]> pq = new PriorityQueue<>((a, b) -> (Integer.compare(a[0], b[0])));
        pq.add(new int[] {0, src, k + 1}); //加入起始城市
        while (!pq.isEmpty()) {
            int[] top = pq.remove();
            int price = top[0];
            int city = top[1];
            int stops = top[2];
            if (city == dst) return price;
            if (stops > 0) {
                Map<Integer, Integer> adj = prices.getOrDefault(city, new HashMap<>());
                for (int a : adj.keySet()) {//取出所有与city这个键对应的value,然后依次价格相加,起始城市更新为下一个城市,即当前city所邻接的城市,同时stops-1,表示当前city的相邻城市停了
                    pq.add(new int[] {price + adj.get(a), a, stops - 1});
                }
            }
        }
        return -1;
    }

8. Employee Importance

题目描述

给你一个员工信息的数据结构,包括员工的唯一ID,他的重要性值和他的直属下属的ID。

例如,员工1是员工2的领导,员工2是员工3的领导。他们分别具有15,10和5的重要性值。然后,员工1具有[1,15,[2]]的数据结构,并且员工2具有[2,10,[3]],员工3具有[3,5,[]]。请注意,虽然员工3也是员工1的下属,但这种关系并不直接。

现在给出公司的员工信息和员工ID,您需要返回此员工及其所有下属的总重要性值。

例1:
输入: [[1,5,[2,3]],[2,3,[]],[3,3,[]]],1
输出: 11

说明
员工1的重要性值为5,他有两个直接下属:员工2和员工3,他们都具有重要性值3,因此员工1的总重要值为5 + 3 + 3 = 11。
注意
一名员工最多只有一名直接领导,可能有几名下属。
员工人数不得超过2000人。

员工数据结构定义

public class Employee {
    // It's the unique id of each node;
    // unique id of this employee
    public int id;
    // the importance value of this employee
    public int importance;
    // the id of direct subordinates
    public List<Integer> subordinates;
}

代码实现(广度优先遍历)

class Solution {
    public int getImportance(List<Employee> employees, int id) {
        int total = 0;
        Map<Integer, Employee> map = new HashMap<>();
        for (Employee employee : employees) {
            map.put(employee.id, employee);
        }
        Queue<Employee> queue = new LinkedList<>();
        queue.offer(map.get(id));
        while (!queue.isEmpty()) {
            Employee current = queue.poll();
            total += current.importance;
            //如果一个顶点有很多个对应的相邻节点,同时这些节点又可能没有关系,就需要利用for循环一次性全部加载进来
            for (int subordinate : current.subordinates) {
                queue.offer(map.get(subordinate));
            }
        }
        return total;
    }
}

代码实现(深度优先遍历)

class Solution {
    public int getImportance(List<Employee> employees, int id) {
        Map<Integer, Employee> map = new HashMap<>();
        for (Employee employee : employees) {
            map.put(employee.id, employee);
        }
        return getImportanceHelper(map, id);
    }
    
    private int getImportanceHelper(Map<Integer, Employee> map, int rootId) {
        Employee root = map.get(rootId);
        int total = root.importance;
        for (int subordinate : root.subordinates) {
            total += getImportanceHelper(map, subordinate);
        }
        return total;
    }
}

9.Word Ladder

问题描述

给定两个单词(beginWord和endWord)和一个字典的单词列表,找到从beginWord到endWord的最短转换序列的长度,例如:

一次只能更改一个字母。
每个转换的单词必须存在于单词列表中。需要注意的是beginWord是可以变换的词。
例如,

例如:
beginWord = "hit"
endWord = "cog"
wordList =["hot","dot","dog","lot","log","cog"]
作为一个最短的转换"hit" -> "hot" -> "dot" -> "dog" -> "cog",
返回它的长度5。

注意
如果没有这样的转换序列,则返回0。
所有单词具有相同的长度。
所有单词只包含小写字母字符。
您可能会认为单词列表中没有重复项。
你可能会认为beginWord和endWord是非空的并且不一样。

问题分析

让我们看看问题陈述中的例子。

start = "hit"

end = "cog"

dict = ["hot", "dot", "dog", "lot", "log"]

由于每次只能更改一个字母,如果我们从"hit"开始,我们只能更改当前单词只有一个字母不相等的单词,例如"hot"。以图论的方式来说,我们可以说这"hot"是一个邻居"hit"。

这个想法很简单,先来看看start的邻居,然后访问其邻居的未访问邻居......嗯,这只是典型的BFS结构。

为了简化问题,我们把end单词插入到dict。一旦我们在BFS 遇到end单词,我们找到了答案。我们为变换的当前距离保留一个变量dist,并在完成一轮BFS搜索(请注意,它应该符合问题描述中距离的定义)后更新它dist++。此外,为避免多次访问某个单词,我们会在访问该单词dict后将其删除。

代码实现

class Solution {
public:
    int ladderLength(string beginWord, string endWord, unordered_set<string>& wordDict) {
        wordDict.insert(endWord); //把目标单词插入到字典中
        queue<string> toVisit; //队列保存单词的邻居,即将要访问的内容
        addNextWords(beginWord, wordDict, toVisit);  
        int dist = 2; //记录单词修改的次数
        while (!toVisit.empty()) {
            int num = toVisit.size();
            for (int i = 0; i < num; i++) { //一次性地把上一个单词的所有相邻单词都提取出来进行计算
                string word = toVisit.front();
                toVisit.pop();
                if (word == endWord) return dist;
                addNextWords(word, wordDict, toVisit); //没找到目标单词,则继续寻找下去
            }
            dist++;
        }
    }
private:
    //本函数的目的是选择当前单词的一个索引位置并依次改为26个字母,也即修改单词,然后找到此单词的邻居,只能一个一个地去试一试。
    void addNextWords(string word, unordered_set<string>& wordDict, queue<string>& toVisit) {
        wordDict.erase(word); //删除访问过的单词
        for (int p = 0; p < (int)word.length(); p++) { //单词的各个位置的字母进行修改
            char letter = word[p];
            for (int k = 0; k < 26; k++) { //修改为26个字母
                word[p] = 'a' + k;
                if (wordDict.find(word) != wordDict.end()) { //如果修改后的单词在字典中存在,则输入到toVisit队列中,表示修改有效
                    toVisit.push(word);
                    wordDict.erase(word);//放入到toVisit中的单词即为将来要访问的单词,为了避免重复访问,所以删除此单词
                }
            }
            word[p] = letter;  //记得把修改后的内容恢复,否则一次就修改了好多几个字母
        } 
    } 
};

参考文献:
[1] Leetcode

[2] 字符串每一次变化一个,从起始单词到目标单词的最短路径,就是一个BFS的计算过程,而且BFS的效率会更高,因为我们知道计算谁最短。
Dijkstra算法利用的是BFS原理实现的,但是通过斐波那契堆实现的Dijkstra算法的时间复杂度为O(n^2),空间复杂度为O(m+nlogn),其中n为顶点个数,m为边数


Dijkstras_progress_animation
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容