算法~深度优先搜索(Depth First Search)一条道走到黑 (附带5道例题)

深度优先搜索

深度优先搜索的方法是,从矩阵中某顶点v出发:
(1)访问顶点v;
(2)依次从v的未被访问的邻接点出发,对矩阵进行深度优先遍历;直至图中和v有路径相通的顶点都被访问;
(3)若此时图中尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先搜索,直到矩阵中需要被访问的顶点均被访问过为止。

扑克与盒子

  • 题目描述
    假如有编号为1 ~ 3的3张扑克牌和编号为1~3的3个盒子,现在需要将3张牌分别放到3个盒子中去,且每个盒子只能放一张牌,一共有多少种不同的放法。
    当走到一个盒子面前的时候,到底要放那一张牌呢?在这里应该把所有的牌都尝试一遍。假设这里约定一个顺序,按牌面值从小到大依次尝试。在这样的假定下,当走到第一个盒子的时候,放入1号牌。
    放好之后,继续向后走,走到第二个盒子面前,此时还剩2张牌,牌面值最小的为2号牌,按照约定的规则,把2号牌放入第二个盒子。
    此时,来到第三个盒子面前,只剩—张牌,放入第三个盒子。此时手中的牌已经用完。
    继续向后走,走到了盒子的尽头,后面融也没有后子并且也没有可用的牌了,此时,一种放法已经完成了,但是这只是一种放法,这条路已经走到了月头,还需要折返,重新回到上一个盒子。
    这里回到第三个盒子,把第三个盒子中的牌更出来,再去尝能否再放其它的牌,这时候手里仍然只有一张3号牌,没有别的选择了,所以还需要继续向后回退,回到2与食子面前。收回2号盒子中的2号牌,现在手里有两张牌,2,3,H约定,再把3号牌放入2号盒子,放好之后,继续向后走,来到3号盒子。
    此事手里只有一张2号牌,把它放入3号盒子,继续向后走。
    此时这条路又一次走到了尽头,一个新的放法又产生了,继续向上抗返、尝试某它可能,按照上述步骤,依次会产生所有结果。
  • 代码
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Created with IntelliJ IDEA.
 * Description: If you don't work hard, you will a loser.
 * User: Listen-Y.
 * Date: 2020-09-21
 * Time: 20:30
 */
public class DepthSearch {
    
    

    //用于存放所有的放置方法
    private List<List<Integer>> all = new ArrayList<>();

    private void DFS (int[] boxes, int[] cards, int index) {
    
    
        //如果此时下标为数组长度, 那么表示牌已发完
        if (index == cards.length) {
    
    
            List<Integer> list = new ArrayList<>();
            for (int i = 1; i < cards.length; i++) {
    
    
                list.add(boxes[i]);
            }
            all.add(list);
            return;
        }
        //依次处理每张牌
        for (int i = 1; i < cards.length; i++) {
    
    
            //判断此时的牌是否被用过
            if (cards[i] == 0) {
    
    
                //使用此时的盒子
                boxes[index] = i;
                //使用牌
                cards[i] = 1;
                //处理下一个盒子
                DFS(boxes, cards, index + 1);
                //回退这张牌
                cards[i] = 0;
            }
        }
    }

    /**
     * 扑克的放置方法
     * @param num
     * @return
     */
    public List<List<Integer>> allStyles(int num) {
    
    
        //获取所有扑克牌的放置顺序
        int[] boxes = new int[num + 1];
        int[] cards = new int[num + 1];
        DFS(boxes, cards, 1);
        return all;

    }


}

员工的重要性

  • 题目描述

给定一个保存员工信息的数据结构,它包含了员工唯一的id,重要度 和 直系下属的id。

比如,员工1是员工2的领导,员工2是员工3的领导。他们相应的重要度为15, 10, 5。那么员工1的数据结构是[1, 15, [2]],员工2的数据结构是[2, 10, [3]],员工3的数据结构是[3, 5, []]。注意虽然员工3也是员工1的一个下属,但是由于并不是直系下属,因此没有体现在员工1的数据结构中。

现在输入一个公司的所有员工信息,以及单个员工id,返回这个员工和他所有下属的重要度之和。

示例 1:

输入: [[1, 5, [2, 3]], [2, 3, []], [3, 3, []]], 1
输出: 11
解释:
员工1自身的重要度是5,他有两个直系下属2和3,而且2和3的重要度均为3。因此员工1的总重要度是 5 + 3 + 3 = 11。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/employee-importance
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

  • 代码
/*
// Definition for Employee.
class Employee {
    public int id;
    public int importance;
    public List<Integer> subordinates;
};
*/

class Solution {
    
    

    private int DFS(Map<Integer, Employee> map, int id) {
    
    

        //通过id找到对应员工
        Employee employee = map.get(id);
        for (int emId : employee.subordinates) {
    
    
            //累加所有员工的重要性
            employee.importance += DFS(map, emId);
        }
        return employee.importance;
    }

    public int getImportance(List<Employee> employees, int id) {
    
    

        if (employees == null) return 0;
        if (employees.size() == 0) return 0;
        //使用map保存此时所有的员工id和与其对应的员工
        Map<Integer, Employee> map = new HashMap<>();
        for (Employee e : employees) {
    
    
            map.put(e.id, e);
        }
        return DFS(map, id);
    }
}

图像渲染

  • 题目渲染

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

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

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

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

示例 1:

输入:
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,
因为它不是在上下左右四个方向上与初始点相连的像素点。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/flood-fill
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

  • 代码
class Solution {
    
    

    private int[][] next = {
    
    {
    
    1, 0}, {
    
    -1, 0}, {
    
    0, 1}, {
    
    0, -1}};

    private void DFS(int[][] image, boolean[][] used, int sr, int sc, int newColor, int oldColor) {
    
    
        //首先将sr sc 设置为newColor
        image[sr][sc] = newColor;
        //设置该点已经被渲染
        used[sr][sc] = true;
        //遍历其上下左右所有节点
        for (int i = 0; i < 4; i++) {
    
    
            int nextSr = sr + next[i][0];
            int nextSc = sc + next[i][1];
            //检查边界
            if (nextSr < 0 || nextSr >= image.length || nextSc < 0 || nextSc >= image[0].length) continue;
            //检查这个点是否是oldColor并且这个点是不是被渲染过
            if (image[nextSr][nextSc] == oldColor && !used[nextSr][nextSc]) {
    
    
                DFS(image, used, nextSr, nextSc, newColor, oldColor);
            }
        }
    }

    public int[][] floodFill(int[][] image, int sr, int sc, int newColor) {
    
    

        int row = image.length;
        if (row == 0) return image;
        int col = image[0].length;
        boolean[][] used = new boolean[row][col];
        DFS(image, used, sr, sc, newColor, image[sr][sc]);
        return image;
    }
}

被围绕的区域

  • 题目描述

给定一个二维的矩阵,包含 ‘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’。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/surrounded-regions
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

  • 代码
class Solution {
    
    

    private int[][] next = {
    
    {
    
    0 ,1}, {
    
    0, -1}, {
    
    1, 0}, {
    
    -1, 0}};

    private void DFS(char[][] board, boolean[][] used, int row, int col) {
    
    
       //将该点置为a
       board[row][col] = 'a';
       used[row][col] = true;
       //遍历其上下左右各点
       for (int i = 0; i < 4; i++) {
    
    
           int newRow = row + next[i][0];
           int newCol = col + next[i][1];
           //检查边界
           if (newRow < 0 || newRow >= board.length || newCol < 0 || newCol >= board[0].length) continue;
           //检查是否为o并且判断是否已经遍历过
           if (board[newRow][newCol] == 'O' && !used[newRow][newCol]) {
    
    
               DFS(board, used, newRow, newCol);
           }
       }

   }

    public void solve(char[][] board) {
    
    
        
        int row = board.length;
        if (row == 0) return;
        int col = board[0].length;
        //检查边界哪有o将其和与其相连的置为'a'
        boolean[][] used = new boolean[row][col];
        for (int i = 0; i < col; i++) {
    
    
            //判断第一行
            if (board[0][i] == 'O') DFS(board, used, 0, i);
            //判断最后一行
            if (board[row - 1][i] == 'O') DFS(board, used, row - 1, i);
        }
        for (int i = 0; i < row; i ++) {
    
    
            //判断第一列
            if (board[i][0] == 'O') DFS(board, used, i, 0);
            //判断最后一列
            if (board[i][col - 1] == 'O') DFS(board, used, i, col - 1);
        }
        //恢复a -> O 并且将其余的o置为x
        for (int i = 0; i < row; i++) {
    
    
            for (int j = 0; j < col; j++) {
    
    
                if (board[i][j] == 'a') {
    
    
                    board[i][j] = 'O';
                } else if (board[i][j] == 'O') {
    
    
                    board[i][j] = 'X';
                }
            }
        }
    }
}

岛屿的数量

  • 题目描述

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

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

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

示例 1:

输入:
[
[‘1’,‘1’,‘1’,‘1’,‘0’],
[‘1’,‘1’,‘0’,‘1’,‘0’],
[‘1’,‘1’,‘0’,‘0’,‘0’],
[‘0’,‘0’,‘0’,‘0’,‘0’]
]
输出: 1
示例 2:

输入:
[
[‘1’,‘1’,‘0’,‘0’,‘0’],
[‘1’,‘1’,‘0’,‘0’,‘0’],
[‘0’,‘0’,‘1’,‘0’,‘0’],
[‘0’,‘0’,‘0’,‘1’,‘1’]
]
输出: 3
解释: 每座岛屿只能由水平和/或竖直方向上相邻的陆地连接而成。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/number-of-islands
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

  • 代码
class Solution {
    
    

    private int[][] next = {
    
    {
    
    1, 0}, {
    
    -1, 0}, {
    
    0, -1}, {
    
    0, 1}};

    private void DFS(char[][] grid, int row, int col) {
    
    
        //现将该点置为0
        grid[row][col] = '0';
        //遍历其上下左右各点
        for (int i = 0; i < 4; i++) {
    
    
            int newRow = row + next[i][0];
            int newCol = col + next[i][1];
            //检验边界合法性
            if (newRow < 0 || newRow >= grid.length || newCol < 0 || newCol >= grid[0].length) continue;
            //判断其是否为1
            if (grid[newRow][newCol] == '1') {
    
    
                DFS(grid, newRow, newCol);
            }
        }
    }

    public int numIslands(char[][] grid) {
    
    

        int row = grid.length;
        if (row == 0) return 0;
        int col = grid[0].length;

        //遍历二维数组 只要发现是1就count++
        int count = 0;
        for (int i = 0; i < row; i++) {
    
    
            for (int j = 0; j < col; j++) {
    
    
                if (grid[i][j] == '1') {
    
    
                    //是陆地
                    count++;
                    DFS(grid, i, j);
                }
            }
        }
        return count;
    }
}

猜你喜欢

转载自blog.csdn.net/Shangxingya/article/details/108720861