算法~广度优先搜索(Breadth First Search)一石激起干层浪(附带6道练习题)

广度优先搜索模型

BFS() {
1.建立起始步骤,队列初始化
2.遍历队列中的每一种可能,whlie(队列不为空)
通过队头元素带出下一步的所有可能,并且依次入队
判断当前情况是否达成目标:按照目标要求处理逻辑
继续遍历队列中的剩余情况
}

出迷宫

假设有一个迷宫,里面有障碍物,迷宫用二维矩阵表示,标记为O的地方表示可以通过,标记为1的地方表示障碍物,不能通过。现在给一个迷宫出口,让你判断是否可以从入口进来之后,走出迷宫,每次可以向任意方向走。
假设是一个10*10的迷宫,入口在(1,1)的位置,出口在(8,10)的位置,通过(1,1)一步可以走到的位置有两个(1,2),(2,1)·但是这两个点并不是出口,需要继续通过这两个位置进一步搜索,假设现在在(1,2),下一次一步可以到达的新的位置为(1,3),(2.2)。而通过(2,1)可以一步到达的新的位置为(2,2),(3,1),但是这里(2,2)是重复的,所以每一个点在走的过程中需要标记是否已经走过了。
两步之后,还没没有走到出口,这时候需要通过新加入的点再去探索下一步能走到哪些新的点上,重复这个过程,直到走到出口为止。

  • 代码
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

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

    static class Node {
    
    
        public int x;
        public int y;

        public Node(int x, int y) {
    
    
            this.x = x;
            this.y = y;
        }
    }

    /**
     * 迷宫问题
     */
    public static void main(String[] args) {
    
    
        int sr, sc, endR, endC;
        Scanner scanner = new Scanner(System.in);
        System.out.print("输入迷宫起点与终点:");
        sr = scanner.nextByte();
        sc = scanner.nextByte();
        endR = scanner.nextByte();
        endC = scanner.nextByte();
        int[][] gird = {
    
    {
    
    0,1,0,0},
                        {
    
    0,0,0,1},
                        {
    
    0,1,0,0},
                        {
    
    0,0,1,0}};
        System.out.println(DFS(gird, sr, sc, endR, endC));

    }

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

    private static boolean DFS(int[][] gird, int sr, int sc, int endR, int endC) {
    
    

        int row = gird.length;
        if (row == 0) {
    
    
            return false;
        }
        int col = gird[0].length;
        //需要一个used保存访问过得点
        boolean[][] used = new boolean[row][col];
        //需要一个队列保存需要遍历的点
        Queue<Node> queue = new LinkedList<>();
        queue.offer(new Node(sr, sc));
        //标记起点已被使用
        used[sr][sc] = true;

        //只要队列不为空就说明还有机会到达终点
        while (!queue.isEmpty()) {
    
    
            //查看起点的四周, 看哪个方向可以走
            for (int i = 0; i < 4; i++) {
    
    
                int newX = queue.peek().x + next[i][0];
                int newY = queue.peek().y + next[i][1];
                //判断边界
                if (newX < 0 || newX >= row || newY < 0 || newY >= col) {
    
    
                    continue;
                }
                //如果此时位置无障碍, 并且未被访问就入队列
                if (gird[newX][newY] == 0 && !used[newX][newY]) {
    
    
                    queue.offer(new Node(newX, newY));
                    //并标记这点被访问过
                    used[newX][newY] = true;
                }
                //如果此时已经是终点就结束方法
                if (newX == endR && newY == endC) {
    
    
                    return true;
                }
            }
            //否则就出队列判断下一个点
            queue.poll();
        }
        return false;
    }
}

员工的重要性

  • 题目描述
    给定一个保存员工信息的数据结构,它包含了员工唯一的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 {
    
    
    public int getImportance(List<Employee> employees, int id) {
    
    
        //将所有员工保存在map中
        Map<Integer, Employee> map = new HashMap<>();
        for (Employee em : employees) {
    
    
            map.put(em.id, em);
        }
        //需要返回的重要性
        int importance = 0;
        //创建一个队列保存下属员工
        Queue<Employee> queue = new LinkedList<>();
        queue.offer(map.get(id));
        while (!queue.isEmpty()) {
    
    
            //获得当前有队列里有几个下属员工
            int size = queue.size();
            while (size-- > 0) {
    
    
                //获取下属
                Employee employ = queue.poll();
                importance += employ.importance;
                //判断当前员工还有没有下属 如果有就加到队列中
                if (employ.subordinates != null) {
    
    
                    for (int curId : employ.subordinates) {
    
    
                        queue.offer(map.get(curId));
                    }
                }
            }
        }
        return importance;
    }
}

N叉树的遍历

  • 题目描述
    给定一个 N 叉树,返回其节点值的层序遍历。 (即从左到右,逐层遍历)。

例如,给定一个 3叉树 :
在这里插入图片描述

返回其层序遍历:

[
[1],
[3,2,4],
[5,6]
]

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

  • 代码
/*
// Definition for a Node.
class Node {
    public int val;
    public List<Node> children;

    public Node() {}

    public Node(int _val) {
        val = _val;
    }

    public Node(int _val, List<Node> _children) {
        val = _val;
        children = _children;
    }
};
*/

class Solution {
    
    
    public List<List<Integer>> levelOrder(Node root) {
    
    
        
        //创建一个队列保存每一层的节点数据
        Queue<Node> queue = new LinkedList<>();
        queue.offer(root);
        List<List<Integer>> ret = new ArrayList<>();
        if (root == null) return ret;
        while (!queue.isEmpty()) {
    
    
            //获取当前层的结点个数
            int size = queue.size();
            List<Integer> list = new ArrayList<>();
            while (size-- > 0) {
    
    
                //将该层所有结点的数据保存在链表中
                Node cur = queue.poll();
                list.add(cur.val);
                //如果当前结点下一层还有结点就入队列
                if (cur.children != null) {
    
    
                    for (Node node : cur.children) {
    
    
                        queue.offer(node);
                    }
                }
            }
            ret.add(list);
        }
        return ret;
    }
}

腐烂的橘子

  • 题目描述
    在给定的网格中,每个单元格可以有以下三个值之一:

值 0 代表空单元格;
值 1 代表新鲜橘子;
值 2 代表腐烂的橘子。
每分钟,任何与腐烂的橘子(在 4 个正方向上)相邻的新鲜橘子都会腐烂。

返回直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1。

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

  • 代码
class Solution {
    
    

    static class Node {
    
    
        public int x;
        public int y;

        public Node(int x, int y) {
    
    
            this.x = x;
            this.y = y;
        }
    }

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

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

        //需要一个队列保存坏的橘子
        Queue<Node> queue = new LinkedList<>();
        //遍历找到坏的橘子
        for (int i = 0; i < row; i++) {
    
    
            for (int j = 0; j < col; j++) {
    
    
                if (grid[i][j] == 2) {
    
    
                    queue.offer(new Node(i, j));
                }
            }
        }
        int time = 0;
        //如果此时有坏的橘子就进行处理感染
        while (!queue.isEmpty()) {
    
    
            //用一个数据判断是否进行了感染
            boolean ok = false;
            int size = queue.size();
            //进行一分钟的感染
            while (size-- > 0) {
    
    
            Node cur = queue.poll();
            //遍历该橘子的四周
            for (int i = 0; i < 4; i++) {
    
    
                int newR = cur.x + next[i][0];
                int newC = cur.y + next[i][1];
                //判断边界
                if (newR < 0 || newR >= row || newC < 0 || newC >= col) {
    
    
                    continue;
                }
                //判断如果当前是新鲜橘子就进行感染
                if (grid[newR][newC] == 1) {
    
    
                    grid[newR][newC] = 2;
                    ok = true;
                    //并将感染后的橘子放到队列中
                    queue.offer(new Node(newR, newC));
                }
            }
        }
        //如果有被感染
        if (ok) {
    
    
            time++;
        }
        }

        //检查如果还有没被感染的橘子就返回-1
        for (int i = 0; i < row; i++) {
    
    
            for (int j = 0; j < col; j++) {
    
    
                if (grid[i][j] == 1) {
    
    
                    return -1;
                }
            }
        }

        return time;
    }
}

单词接龙

  • 题目描述
    给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。转换需遵循如下规则:

每次转换只能改变一个字母。
转换过程中的中间单词必须是字典中的单词。
说明:

如果不存在这样的转换序列,返回 0。
所有单词具有相同的长度。
所有单词只由小写字母组成。
字典中不存在重复的单词。
你可以假设 beginWord 和 endWord 是非空的,且二者不相同。

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

  • 代码
class Solution {
    
    
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
    
    
        //将所有单词放到hash表中 便于查询
        Set<String> dict = new HashSet<>();
        for (String word : wordList) {
    
    
            dict.add(word);
        }
        //判断此时字典是否有这个endWord
        if (!dict.contains(endWord)) {
    
    
            return 0;
        }
        //使用一个set去保存访问过的单词
        Set<String> used = new HashSet<>();
        used.add(beginWord);
        //使用一个队列去保存替换一次的单词
        Queue<String> queue = new LinkedList<>();
        queue.offer(beginWord);
        //计数器
        int step = 1;

        while (!queue.isEmpty()) {
    
    
            //取出一次变化后的说有单词
            int size = queue.size();
            while (size-- > 0) {
    
    
                String curWord = queue.poll();
                //对这个单词进行每一个位置的每一个字符的替换
                for (int i = 0; i < curWord.length(); i++) {
    
    
                    StringBuilder builder = new StringBuilder(curWord);
                    for (char ch = 'a'; ch <= 'z'; ch++) {
    
    
                        builder.setCharAt(i, ch);
                        String newWord = builder.toString();
                        //如果此时就是endWord就结束
                        if (newWord.equals(endWord)) {
    
    
                            return step + 1;
                        }
                        //判断这个新单词是否在字典中 没有被访问过
                        if (dict.contains(newWord) && !used.contains(newWord)) {
    
    
                            //否则入队列
                            queue.offer(newWord);
                            used.add(newWord);
                        }
                    }
                }
            }
            //完成所有size表示进行了一次改变
            step++;
        }
        //如果到这还没有返回说明不能扎到
        return 0;
    }
}

打开转盘锁

  • 题目描述
    你有一个带有四个圆形拨轮的转盘锁。每个拨轮都有10个数字: ‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’ 。每个拨轮可以自由旋转:例如把 ‘9’ 变为 ‘0’,‘0’ 变为 ‘9’ 。每次旋转都只能旋转一个拨轮的一位数字。

锁的初始数字为 ‘0000’ ,一个代表四个拨轮的数字的字符串。

列表 deadends 包含了一组死亡数字,一旦拨轮的数字和列表里的任何一个元素相同,这个锁将会被永久锁定,无法再被旋转。

字符串 target 代表可以解锁的数字,你需要给出最小的旋转次数,如果无论如何不能解锁,返回 -1。

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

  • 代码
class Solution {
    
    
    public int openLock(String[] deadends, String target) {
    
    
        //用一个hash去保存dead便于查找
        Set<String> deadSet = new HashSet<>();
        for (String str : deadends) {
    
    
            deadSet.add(str);
        }
        //如果0000是死亡数字那么永远有达不到
        if (deadSet.contains("0000")) {
    
    
            return -1;
        }
        //如果当前就是0000 就返回0
        if (target.equals("0000")) {
    
    
            return 0;
        }
        //用一个队列去保存此时的转盘上的数字
        Queue<String> queue = new LinkedList<>();
        queue.offer("0000");
        //计数器
        int step = 0;
        //用一个set保存访问过得密码
        Set<String> used = new HashSet<>();

        while (!queue.isEmpty()) {
    
    
            int size = queue.size();
            while (size-- > 0) {
    
    
                String curStr = queue.poll();
                //进行一次的拨盘
                for (int i = 0; i < 4; i++) {
    
    
                    //一次波动有俩种可能
                    char newOne;
                    char newTwo;
                    //对curStr当前的字符是0/9要进行特殊处理
                    if (curStr.charAt(i) == '0' || curStr.charAt(i) == '9') {
    
    
                        if (curStr.charAt(i) == '0') {
    
    
                            newOne = '1';
                            newTwo = '9';
                        } else {
    
    
                            newOne = '0';
                            newTwo = '8';
                        }
                    } else {
    
    
                        newOne = (char) (curStr.charAt(i) + 1);
                        newTwo = (char) (curStr.charAt(i) - 1);
                    }
                    //将拨好的单个数字进行与其他三个字符想组合
                    StringBuilder oneBuilder = new StringBuilder(curStr);
                    StringBuilder twoBuilder = new StringBuilder(curStr);
                    oneBuilder.setCharAt(i, newOne);
                    twoBuilder.setCharAt(i, newTwo);
                    String oneStr = oneBuilder.toString();
                    String twoStr = twoBuilder.toString();
                    //对此时这俩个字符串进行判断
                    // 如果已经是target就返回操作步数
                    if (oneStr.equals(target) || twoStr.equals(target)) {
    
    
                        return step + 1;
                    }
                    //分别对俩个字符串进行判断是否为锁死密码和是否已经遍历过
                    if (!deadSet.contains(oneStr) && !used.contains(oneStr)) {
    
    
                        queue.offer(oneStr);
                        used.add(oneStr);
                    }
                    if (!deadSet.contains(twoStr) && !used.contains(twoStr)) {
    
    
                        queue.offer(twoStr);
                        used.add(twoStr);
                    }
                }
            }
            step++;
        }
        return -1;
    }
}

猜你喜欢

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