BFS算法-leetcode java题解

news2025/1/16 1:05:17

BFS算法-leetcode java题解

本文目录

    • BFS算法-leetcode java题解
      • BFS算法思想
      • leetcode 111. 二叉树的最小深度
      • leetcode 1091. 二进制矩阵中的最短路径
      • leetcode 752. 打开转盘锁
      • leetcode 127. 单词接龙
      • leetcode 433. 最小基因变化
      • leetcode 1162. 地图分析
      • leetcode 695. 岛屿的最大面积
      • leetcode 207. 课程表
      • leetcode 210. 课程表 II
      • 双向BFS
      • leetcode 752. 打开转盘锁 双向bfs
      • leetcode 433. 最小基因变化 双向bfs

BFS算法思想

BFS的核心思想:把一些问题抽象成图,从一个点开始,向四周开始扩散。
一般来说,BFS算法都是用「队列」这种数据结构,每次将一个节点周围的所有节点加入队列。

BFS 相对 DFS 的最主要的区别是:BFS 找到的路径一定是最短的,但代价就是空间复杂度比 DFS 大很多

BFS问题的本质就是在一幅「图」中找到从起点start到终点target的最近距离

// 计算从起点 start 到终点 target 的最近距离
int BFS(Node start, Node target) {
    Queue<Node> q; // 核心数据结构
    Set<Node> visited; // 避免走回头路

    q.offer(start); // 将起点加入队列
    visited.add(start);
    int step = 0; // 记录扩散的步数

    while (!q.isEmpty()) {
        int sz = q.size();
        /* 将当前队列中的所有节点向四周扩散 */
        for (int i = 0; i < sz; i++) {
            Node cur = q.poll();
            /* 划重点:这里判断是否到达终点 */
            if (cur is target)
                return step;
            /* 将 cur 的相邻节点加入队列 */
            for (Node x : cur.adj())
                if (x not in visited) {
                    q.offer(x);
                    visited.add(x);
                }
        }
        /* 划重点:更新步数在这里 */
        step++;
    }
}

BFS 的核心数据结构;cur.adj()泛指cur相邻的节点,比如说二维数组中,cur上下左右四面的位置就是相邻节点;visited的主要作用是防止走回头路,大部分时候都是必须的,但是像一般的二叉树结构,没有子节点到父节点的指针,不会走回头路就不需要visited

leetcode 111. 二叉树的最小深度

给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明:叶子节点是指没有子节点的节点。

输入:root = [3,9,20,null,null,15,7]
输出:2

输入:root = [2,null,3,null,4,null,5,null,6]
输出:5
    public int minDepth(TreeNode root) {
        if (root == null)  return 0;
        int depth = 1;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);

        while(!queue.isEmpty()){
            int sz = queue.size();
            for (int i = 0; i < sz; i++) {
                TreeNode cur = queue.poll();
                if (cur.left == null && cur.right == null)
                    return depth;
                if (cur.left != null)
                    queue.offer(cur.left);
                if (cur.right != null)
                    queue.offer(cur.right);
            }
            depth ++;
        }
        return depth;
    }

leetcode 1091. 二进制矩阵中的最短路径

给你一个 n x n 的二进制矩阵 grid 中,返回矩阵中最短 畅通路径 的长度。如果不存在这样的路径,返回 -1 。
二进制矩阵中的 畅通路径 是一条从 左上角 单元格(即,(0, 0))到 右下角 单元格(即,(n - 1, n - 1))的路径,该路径同时满足下述要求:
路径途经的所有单元格都的值都是 0 。
路径中所有相邻的单元格应当在 8 个方向之一 上连通(即,相邻两单元之间彼此不同且共享一条边或者一个角)。
畅通路径的长度 是该路径途经的单元格总数。

输入:grid = [[0,1],[1,0]]
输出:2

输入:grid = [[0,0,0],[1,1,0],[1,1,0]]
输出:4
    public static int shortestPathBinaryMatrix(int[][] grid) {
        if(grid == null || grid.length == 0 || grid[0].length == 0){
            return -1;
        }
        if(grid[0][0] == 1){
            return -1;
        }
        int[][] directions = {{-1, -1}, {-1, 0}, {-1, 1}, {0, 1}, {1, 1}, {1, 0}, {1, -1}, {0, -1}};//定义 8个方向
        //bfs的老套路 来个队列,数据结构为数组int[]
        Queue<int[]> queue = new LinkedList<>();
        int m = grid.length, n = grid[0].length;
        queue.add(new int[]{0, 0});//把起点扔进去

        grid[0][0] = 1;// 因为从出发点开始找寻路径,所以出发点不能再被经过,将其设为1
        int step = 1;

        while(!queue.isEmpty()){
            int sz = queue.size();
            for(int i = 0; i < sz; i++){  // 依次取出队列的每个节点并进行遍历
                int[] cur = queue.poll();
                // 如果等于终点则返回
                if(cur[0] == m - 1 && cur[1] == n - 1){
                    return step;
                }
                // 对于当前节点,对其8个方向进行搜寻
                for(int[] d : directions){
                    int row = cur[0] + d[0];
                    int col = cur[1] + d[1];//当前节点的某个方向上的节点坐标
                    if(row < 0 || row >= m || col < 0 || col >= n || grid[row][col] == 1){
                        continue;
                    }
                    queue.add(new int[]{row, col});
                    grid[row][col] = 1;// 某方向的节点被经历过后,将其标记为1,以后不再经过它
                }
            }
            step++;// 一轮队列遍历完以后,即节点周围的一层节点被遍历完以后,路径长度+1
        }
        return -1;
    }

leetcode 752. 打开转盘锁

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

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

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

字符串 target 代表可以解锁的数字,你需要给出解锁需要的最小旋转次数,如果无论如何不能解锁,返回 -1 。
输入:deadends = ["0201","0101","0102","1212","2002"], target = "0202"
输出:6
解释:
可能的移动序列为 "0000" -> "1000" -> "1100" -> "1200" -> "1201" -> "1202" -> "0202"。
注意 "0000" -> "0001" -> "0002" -> "0102" -> "0202" 这样的序列是不能解锁的,
因为当拨动到 "0102" 时这个锁就会被锁定。

输入: deadends = ["8888"], target = "0009"
输出:1
解释:把最后一位反向旋转一次即可 "0000" -> "0009"。

输入: deadends = ["8887","8889","8878","8898","8788","8988","7888","9888"], target = "8888"
输出:-1
解释:无法旋转到目标数字且不被锁定。
class Solution {
    public int openLock(String[] deadends, String target) {
        Set<String> dead = new HashSet<>();
        for(String d: deadends){
            dead.add(d);
        }
        if (dead.contains("0000")) {
            return -1;
        }

        Queue<String> queue = new LinkedList<>();
        Set<String> visited = new HashSet<>();
        queue.offer("0000");
        visited.add("0000");
        int path = 0;

        while(!queue.isEmpty()){
            int sz = queue.size();
            for(int i = 0; i < sz; i++){
                String cur = queue.poll();
                if(cur.equals(target)){
                    return path;
                }
                for(int j = 0; j < cur.length(); j++){
                    String str = plusOne(cur, j);
                    if(!visited.contains(str) && !dead.contains(str)){
                        queue.offer(str);
                        visited.add(str);
                    }
                    String strr = minusOne(cur, j);
                    if(!visited.contains(strr) && !dead.contains(strr)){
                        queue.offer(strr);
                        visited.add(strr);
                    }
                }
            }
            path ++;
        }
        return -1;
    }

    public String plusOne(String cur, int j){
        char[] ch = cur.toCharArray();
        if(ch[j] == '9'){
            ch[j] = '0';
        }else{
            ch[j] ++;
        }
        return String.valueOf(ch);
    }

    public String minusOne(String cur, int j){
        char[] ch = cur.toCharArray();
        if(ch[j] == '0'){
            ch[j] = '9';
        }else{
            ch[j] --;
        }
        return String.valueOf(ch);
    }
}

leetcode 127. 单词接龙

字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列 beginWord -> s1 -> s2 -> ... -> sk:

每一对相邻的单词只差一个字母。
 对于 1 <= i <= k 时,每个 si 都在 wordList 中。注意, beginWord 不需要在 wordList 中。
sk == endWord
给你两个单词 beginWord 和 endWord 和一个字典 wordList ,返回 从 beginWord 到 endWord 的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0 
输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
输出:5
解释:一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog", 返回它的长度 5。
    public int ladderLength(String beginWord, String endWord, List<String> wordList) {

        if(!wordList.contains(endWord) || beginWord.length() != endWord.length()){
            return 0;
        }
        Set<String> path = new HashSet<>();
        for(String s: wordList){
            path.add(s);
        }

        Queue<String> queue = new LinkedList<>();
        Set<String> hasUsed = new HashSet<>();

        queue.offer(beginWord);
        hasUsed.add(beginWord);
        int road = 1;

        while(!queue.isEmpty()){
            int sz = queue.size();
            for(int i = 0; i < sz; i++){
                String cur = queue.poll();
                if(cur.equals(endWord)){
                    return road;
                }
                for(int j = 0; j < beginWord.length(); j++){
                    List<String> newWords =  changeWord(cur, j, path);
                    for (String nei : newWords) {
                        if (!hasUsed.contains(nei)) {
                            queue.offer(nei);
                            hasUsed.add(nei);
                        }
                    }
                }
            }
            road ++;
        }
        return 0;
    }

    public List<String> changeWord(String cur, int j, Set<String> path){
        List<String> res = new LinkedList<>();
        char[] chstr = cur.toCharArray();
        for(char ch = 'a'; ch <= 'z'; ch ++){
            if(ch == cur.charAt(j)){
                continue;
            }
            chstr[j] = ch;
            String newS = String.valueOf(chstr);
            if(path.contains(newS))
                res.add(newS);
        }
        return res;
    }

leetcode 433. 最小基因变化

基因序列可以表示为一条由 8 个字符组成的字符串,其中每个字符都是 'A'、'C'、'G' 和 'T' 之一。

假设我们需要调查从基因序列 start 变为 end 所发生的基因变化。一次基因变化就意味着这个基因序列中的一个字符发生了变化。

例如,"AACCGGTT" --> "AACCGGTA" 就是一次基因变化。
另有一个基因库 bank 记录了所有有效的基因变化,只有基因库中的基因才是有效的基因序列。(变化后的基因必须位于基因库 bank 中)

给你两个基因序列 start 和 end ,以及一个基因库 bank ,请你找出并返回能够使 start 变化为 end 所需的最少变化次数。如果无法完成此基因变化,返回 -1 。

注意:起始基因序列 start 默认是有效的,但是它并不一定会出现在基因库中。
输入:start = "AACCGGTT", end = "AACCGGTA", bank = ["AACCGGTA"]
输出:1

输入:start = "AACCGGTT", end = "AAACGGTA", bank = ["AACCGGTA","AACCGCTA","AAACGGTA"]
输出:2

输入:start = "AAAAACCC", end = "AACCCCCC", bank = ["AAAACCCC","AAACCCCC","AACCCCCC"]
输出:3
    public int minMutation(String startGene, String endGene, String[] bank) {
        Set<String> path = new HashSet<>();
        for(String s : bank){
            path.add(s);
        }

        Queue<String> queue = new LinkedList<>();
        Set<String> hasUsed = new HashSet<>();
        queue.offer(startGene);
        hasUsed.add(startGene);
        int res = 0;
        char[] keys = {'A', 'C', 'G', 'T'};

        while(!queue.isEmpty()){
            int sz = queue.size();
            for(int i = 0; i < sz; i++){
                String cur = queue.poll();
                if(endGene.equals(cur)){
                    return res;
                }
                for(int j = 0; j < startGene.length(); j++){
                    List<String> temp = changeOne(cur, j, keys);
                    for(String cc: temp){
                        if(path.contains(cc) && !hasUsed.contains(cc)){
                            queue.offer(cc);
                            hasUsed.add(cc);
                        }
                    }
                }
            }
            res ++;
        }
        return -1;
    }
    // A C G T => 0,1,2,3
    public List<String> changeOne(String cur, int j, char[] keys){
        List<String> list = new LinkedList<>();
        char[] ch = cur.toCharArray();

        for(int i = 0; i <= 3; i++){
            if(ch[j] == keys[i]){
                continue;
            }else{
                ch[j] = keys[i];
            }
            list.add(String.valueOf(ch));
        }
        return list;
    }

leetcode 1162. 地图分析

你现在手里有一份大小为 n x n 的 网格 grid,上面的每个 单元格 都用 0 和 1 标记好了。其中 0 代表海洋,1 代表陆地。

请你找出一个海洋单元格,这个海洋单元格到离它最近的陆地单元格的距离是最大的,并返回该距离。如果网格上只有陆地或者海洋,请返回 -1。

我们这里说的距离是「曼哈顿距离」( Manhattan Distance):(x0, y0) 和 (x1, y1) 这两个单元格之间的距离是 |x0 - x1| + |y0 - y1| 。
输入:grid = [[1,0,1],[0,0,0],[1,0,1]]
输出:2
解释: 
海洋单元格 (1, 1) 和所有陆地单元格之间的距离都达到最大,最大距离为 2。
    public static  int maxDistance(int[][] grid) {
        Queue<int[]> queue = new LinkedList<>();
        int flag = 0;
        for(int i = 0; i < grid.length; i++){
            for(int j = 0; j < grid[0].length; j++){
                if(grid[i][j] == 1){
                    queue.offer(new int[]{i, j});
                    flag ++;
                }
            }
        }
        if(flag ==  grid.length * grid[0].length){
            return -1;
        }

        int[][] d = {{0,1}, {0,-1}, {1,0}, {-1,0}};

        int[] point = null;
        while(!queue.isEmpty()){
            int sz = queue.size();
            for(int i = 0; i < sz; i++){
                point = queue.poll();
                for(int j = 0; j < d.length; j++){
                    int row = point[0] + d[j][0];
                    int col = point[1] + d[j][1];
                    if(row < 0 || row >= grid.length || col < 0 || col >= grid[0].length || grid[row][col] != 0){
                        continue;
                    }
                    queue.add(new int[]{row, col});
                    grid[row][col] = grid[point[0]][point[1]] + 1;
                }
            }
        }

        if (point == null) {
            return -1;
        }
        return grid[point[0]][point[1]] - 1;
    }

leetcode 695. 岛屿的最大面积

给你一个大小为 m x n 的二进制矩阵 grid 。

岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在 水平或者竖直的四个方向上 相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。

岛屿的面积是岛上值为 1 的单元格的数目。

计算并返回 grid 中最大的岛屿面积。如果没有岛屿,则返回面积为 0 。
输入:grid = [[0,0,1,0,0,0,0,1,0,0,0,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,1,1,0,1,0,0,0,0,0,0,0,0],[0,1,0,0,1,1,0,0,1,0,1,0,0],[0,1,0,0,1,1,0,0,1,1,1,0,0],[0,0,0,0,0,0,0,0,0,0,1,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,0,0,0,0,0,0,1,1,0,0,0,0]]
输出:6
解释:答案不应该是 11 ,因为岛屿只能包含水平或垂直这四个方向上的 1 。

输入:grid = [[0,0,0,0,0,0,0,0]]
输出:0
    public int maxAreaOfIsland(int[][] grid) {
        int m = grid.length;
        int n = grid[0].length;
        int res = 0;
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                if(grid[i][j] == 1){
                    res = Math.max(res, bfs(grid, i, j));
                }
            }
        }
        return res;
    }

    public int bfs(int[][] grid, int i, int j){
        int[][] d = new int[][]{{1,0},{0,1},{-1,0},{0,-1}};
        Queue<int[]> queue = new LinkedList<>();
        queue.offer(new int[]{i,j});
        grid[i][j] = 0;
        int dis = 1;
        while(!queue.isEmpty()){
            int sz = queue.size();
            for(int x = 0; x < sz; x++){
                int[] cur = queue.poll();
                for(int y = 0; y < 4; y ++){
                    int a = cur[0] + d[y][0];
                    int b = cur[1] + d[y][1];
                    if(a >= 0 && a < grid.length && b >= 0 && b < grid[0].length && grid[a][b] == 1){
                        grid[a][b] = 0;
                        queue.offer(new int[]{a,b});
                        dis ++;
                    }
                }
            }
        }
        return dis;
    }

leetcode 207. 课程表

你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。

在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程  bi 。

例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。
请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false
输入:numCourses = 2, prerequisites = [[1,0]]
输出:true
解释:总共有 2 门课程。学习课程 1 之前,你需要完成课程 0 。这是可能的。

输入:numCourses = 2, prerequisites = [[1,0],[0,1]]
输出:false
解释:总共有 2 门课程。学习课程 1 之前,你需要先完成​课程 0 ;并且学习课程 0 之前,你还应先完成课程 1 。这是不可能的。

使用图的邻接矩阵存储,入度为0的点没有前驱节点

class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
        int[] inDegree = new int[numCourses]; // 存储入度
        Map<Integer, List<Integer>> map = new HashMap<>(); // 构建邻接表

        // 初始化入度和邻接表
        for (int i = 0; i < prerequisites.length; i++) {
            inDegree[prerequisites[i][0]]++; // 记录入度
            if (map.containsKey(prerequisites[i][1])) { // 加入邻接表
                map.get(prerequisites[i][1]).add(prerequisites[i][0]);
            } else {
                List<Integer> list = new ArrayList<>();
                list.add(prerequisites[i][0]);
                map.put(prerequisites[i][1], list);
            }
        }

        Queue<Integer> queue = new LinkedList<>();
        int count = 0;
        for (int i = 0; i < numCourses; i++) {
            if (inDegree[i] == 0) {
                queue.add(i);
            }
        }

        while (!queue.isEmpty()) {
            int sz = queue.size();
            for(int i = 0; i < sz; i++){
                count ++;
                int cur = queue.poll();
                List<Integer> list = map.get(cur);
                if (list == null) continue;
                for (int j = 0; j < list.size(); j++) {
                    inDegree[list.get(j)] --;
                    if (inDegree[list.get(j)] == 0) {
                        queue.offer(list.get(j));
                    }
                }
            }
        }
        return count == numCourses;
    }
}

leetcode 210. 课程表 II

现在你总共有 numCourses 门课需要选,记为 0 到 numCourses - 1。给你一个数组 prerequisites ,其中 prerequisites[i] = [ai, bi] ,表示在选修课程 ai 前 必须 先选修 bi 。

例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示:[0,1] 。
返回你为了学完所有课程所安排的学习顺序。可能会有多个正确的顺序,你只要返回 任意一种 就可以了。如果不可能完成所有课程,返回 一个空数组 。
输入:numCourses = 2, prerequisites = [[1,0]]
输出:[0,1]
解释:总共有 2 门课程。要学习课程 1,你需要先完成课程 0。因此,正确的课程顺序为 [0,1] 。

输入:numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]]
输出:[0,2,1,3]
解释:总共有 4 门课程。要学习课程 3,你应该先完成课程 1 和课程 2。并且课程 1 和课程 2 都应该排在课程 0 之后。
因此,一个正确的课程顺序是 [0,1,2,3] 。另一个正确的排序是 [0,2,1,3] 。

只记录入度,每次都去遍历数组,减入度,减到0则加入路径

    public static int[] findOrder(int numCourses, int[][] prerequisites) {
        int[] inDegree = new int[numCourses]; // 存储入度
        for (int i = 0; i < prerequisites.length; i++) {
            inDegree[prerequisites[i][0]]++; // 记录入度
        }

        Queue<Integer> queue = new LinkedList<>();
        int count = 0;
        for (int i = 0; i < numCourses; i++) {
            if (inDegree[i] == 0) {
                queue.add(i);
            }
        }

        int[] path = new int[numCourses];  // 可以学完的课程
        while (!queue.isEmpty()) {
            int sz = queue.size();
            for(int i = 0; i < sz; i++){
                int cur = queue.poll();
                path[count] = cur;
                count ++;
                for (int[] p : prerequisites) {
                    if (p[1] == cur){
                        inDegree[p[0]]--;
                        if (inDegree[p[0]] == 0){
                            queue.offer(p[0]);
                        }
                    }
                }
            }
            if(count == numCourses){
                break;
            }
        }

        if(count == numCourses){
            return path;
        }else{
            return new int[]{};
        }
        
    }

双向BFS

双向 BFS 其实只遍历了半棵树就出现了交集,也就是找到了最短距离,双向 BFS 比传统 BFS 高效

双向 BFS 也有局限,因为你必须知道终点在哪里

leetcode 752. 打开转盘锁 双向bfs

    public static int openLock(String[] deadends, String target) {
        Set<String> deads = new HashSet<>();
        for (String s : deadends) deads.add(s);
        // 用集合不用队列,可以快速判断元素是否存在
        Set<String> q1 = new HashSet<>();
        Set<String> q2 = new HashSet<>();
        Set<String> visited = new HashSet<>();

        int step = 0;
        q1.add("0000");
        q2.add(target);

        while (!q1.isEmpty() && !q2.isEmpty()) {
            // 哈希集合在遍历的过程中不能修改,用 temp 存储扩散结果
            Set<String> temp = new HashSet<>();

            /* 将 q1 中的所有节点向周围扩散 */
            for (String cur : q1) {
                /* 判断是否到达终点 */
                if (deads.contains(cur))
                    continue;
                if (q2.contains(cur))
                    return step;
                visited.add(cur);

                /* 将一个节点的未遍历相邻节点加入集合 */
                for (int j = 0; j < 4; j++) {
                    String up = plusOne(cur, j);
                    if (!visited.contains(up))
                        temp.add(up);
                    String down = minusOne(cur, j);
                    if (!visited.contains(down))
                        temp.add(down);
                }
            }
            /* 在这里增加步数 */
            step++;
            // temp 相当于 q1
            // 这里交换 q1 q2,下一轮 while 就是扩散 q2
            q1 = q2;
            q2 = temp;
        }
        return -1;
    }
  1. 使用 HashSet 方便快速判断两个集合是否有交集**
  2. while 循环的最后交换q1和q2的内容,所以只要默认扩散q1就相当于轮流扩散q1和q2

双向 BFS 优化

// ...
while (!q1.isEmpty() && !q2.isEmpty()) {
    if (q1.size() > q2.size()) {
        // 交换 q1 和 q2
        temp = q1;
        q1 = q2;
        q2 = temp;
    }
}

按照 BFS 的逻辑,队列(集合)中的元素越多,扩散之后新的队列(集合)中的元素就越多;在双向 BFS 算法中,如果我们每次都选择一个较小的集合进行扩散,那么占用的空间增长速度就会慢一些,效率就会高一些

leetcode 433. 最小基因变化 双向bfs

    public static int minMutation(String startGene, String endGene, String[] bank) {

        Set<String> path = new HashSet<>();
        for(String s : bank){
            path.add(s);
        }
        if(!path.contains(endGene)){
            return -1;
        }

        Set<String> q1 = new HashSet<>();
        Set<String> q2 = new HashSet<>();
        Set<String> hasUsed = new HashSet<>();
        q1.add(startGene);
        q2.add(endGene);
        hasUsed.add(startGene);

        int res = 0;
        char[] keys = {'A', 'C', 'G', 'T'};

        while(!q1.isEmpty() && !q2.isEmpty()){
            Set<String> temp = new HashSet<>();
            for(String cur: q1){
                if (q2.contains(cur)){
                    return res;
                }
                hasUsed.add(cur);
                for(int j = 0; j < startGene.length(); j++){
                    List<String> nowList = changeOne(cur, j, keys);
                    for(String cc: nowList){
                        if(path.contains(cc) && !hasUsed.contains(cc)){
                            temp.add(cc);
                        }
                    }
                }
            }
            res ++;
            q1 = q2;
            q2 = temp;
        }
        return -1;
    }
    // A C G T => 0,1,2,3
    public static List<String> changeOne(String cur, int j, char[] keys){
        List<String> list = new LinkedList<>();
        char[] ch = cur.toCharArray();

        for(int i = 0; i < 4; i++){
            if(ch[j] == keys[i]){
                continue;
            }else{
                ch[j] = keys[i];
            }
            list.add(String.valueOf(ch));
        }
        return list;
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/101029.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

记一次Spring4shell漏洞分析

漏洞条件 1.Tomcat war包部署 Tomcat 9.60<&#xff08;Tomcat9.61已打补丁&#xff09; 1.Web应用以war包部署到Tomcat中时使用到ParallelWebappClassLoader 2.而以jar包部署的classLoader嵌套参数被解析为org.springframework.boot.loader.LaunchedURLClassLoader&#…

OFDM系统同步技术的matlab仿真,包括符号定时同步,采样钟同步,频偏估计

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 在单载波系统中&#xff0c;载波频率的偏移只会对接收信号造成一定的幅度衰减和相位旋转&#xff0c;这可以通过均衡等方法来加以克服。而对于多载波系统来说&#xff0c;载波频率的偏移会导致子…

拖动布局的两种方式

一种是弹窗的拖动布局&#xff0c;一种是非弹窗。 代码如下&#xff1a; 非弹窗&#xff1a;这里加载了一个本地的视频 import android.content.Context; import android.content.SharedPreferences; import android.os.Bundle; import android.view.MotionEvent; import an…

Go1.19.3 map原理简析

map简析 map是一个集数组与链表(美貌与智慧)特性于一身的数据结构&#xff0c;其增删改查时间复杂度都非常优秀&#xff0c;在很多场景下用其替代树结构。关于map的特性请自行学习。 Go语言在语言层面就支持了map&#xff0c;而非其他语言(如Java)通过外置类库的方式实现。 使…

【LSSVM回归预测】基于matlab天鹰算法优化最小二乘支持向量机AO-LSSVM数据回归预测【含Matlab源码 1848期】

⛄一、天鹰算法优化最小二乘支持向量机LSSVM简介 1 最小二乘支持向量机 最小二乘支持向量机是支持向量机的一种改进算法[9,10],它将SVM算法中的不等式约束转化为等式约束,采用最小二乘线性系统作为损失函数代替支持向量机所采用的二次规划方法,以下介绍了该方法的基本原理。 …

DSPE-PEG-DBCO磷脂聚乙二醇二苯基环辛炔简介

名称 磷脂聚乙二醇二苯基环辛炔 DSPE-PEG-DBCO 中文名称 二硬脂酰基磷脂酰乙醇胺-聚乙二醇-二苯基环辛炔 英文名称 DSPE-PEG-DBCO 溶剂 部分常规有机溶剂 存储条件 -20冷冻保存&#xff0c;惰性气体保护 保存时间 1年 PEG常规分子量 2000 3400 5000 DSPE-PEG-DBCO DSPE&a…

typescript 类型运算探讨

以函数的方式来看typescript 类型运算尖括号 <>TypeScript 类型运算符1、extends2、keyof3、infer4、in题目示例对于初接触typescript的前端开发者来说&#xff0c;我们可能会对typescript的类型运算感到很陌生和难以理解。现在想说的是&#xff0c;其实我们可以换另外一…

我国服务行业体经济尽显强大韧性 2021年全年总体保持恢复性增长态势

根据观研报告网发布的《2022年中国服务行业分析报告-行业发展监测与投资潜力分析》显示&#xff0c;所谓服务业&#xff0c;不是指商品的买卖&#xff0c;而是指通过各种不同的服务工作&#xff0c;让顾客得到满足。 服务业有传统服务业与现代服务业之分。传统服务业是指为人们…

【云原生进阶之容器】第一章Docker核心技术1.7节——Docker镜像技术剖析

1 容器镜像概述 1.1 什么是镜像 镜像就是一个可执行独立运行的软件包。包含应用运行所必须的文件和依赖包;镜像可以理解为类或者模板,只要在容器的环境下开箱即用; Docker容器与镜像的关系: 1.2 bootfs和rootfs 通常而言,Linux的操作系统由两类文件系统组成:bootfs…

一、Kubernetes基本介绍和功能架构

1、kubernetes 概述 kubernetes&#xff0c;简称 K8s&#xff0c;是用 8 代替 8 个字符“ubernete”而成的缩写。是一个开源 的&#xff0c;用于管理云平台中多个主机上的容器化的应用&#xff0c;Kubernetes 的目标是让部署容器化的 应用简单并且高效(powerful),Kubernetes 提…

python中的异常捕获与传递

目录 一.了解异常 二.异常的捕获方法 1.捕获常规异常 演示 2.捕获制定异常 演示 3.捕获多个异常 演示 4.捕获所有异常 演示 5.异常else 演示 6.异常的finally 演示 三.异常的传递 一.了解异常 当我们的程序遇到了BUG&#xff0c;那么接下来有两种情况: 整个程序因…

leetcode.1971 寻找图中是否存在路径 - bfs dfs 并查集 邻接表 邻接矩阵

1971. 寻找图中是否存在路径 目录 1、bfs - 邻接矩阵 2、dfs - 邻接表 3、并查集 1、bfs - 邻接矩阵 让源点source入队 取队头遍历未标记的邻接节点并入队如果队伍里有dest目标节点 说明dest被遍历到 则return trueclass Solution { public:static const int N2*1e5;bool st[…

靴子落地:ChatGPT 国内发展或被「拉手刹」

内容一览&#xff1a;深度合成服务在满足用户需求、改进用户体验的同时&#xff0c;也被一些不法人员用于制作、复制、发布、传播违法信息&#xff0c;诋毁、贬损他人名誉、荣誉&#xff0c;仿冒他人身份实施诈骗等违法行为&#xff0c;如今针对这一技术的管理规定终于发布了。…

游戏开发 mask和RectMask2D区别

1. Mask遮罩的大小与形状依赖于Graphic&#xff0c;而RectMask2D只需要依赖RectTransform 2. Mask支持圆形或其他形状遮罩&#xff0c; 而RectMask2D只支持矩形 3. Mask会增加drawcall 4、mask的性质&#xff1a; 性质1&#xff1a;Mask会在首尾&#xff08;首Mask节点&…

Jenkins(4)— 配置钉钉通知

1、创建钉钉机器人 自定义机器人接入 - 钉钉开放平台 群设置 >> 智能群助手 >> 添加机器人 >> 自定义webhook机器人 输入自定义钉钉机器人名字和选择必要的安全设置。 注意&#xff1a;复制上面的加密信息和Webhook&#xff0c;后面在jenkins配置钉钉机器…

[附源码]计算机毕业设计Python的家政服务平台(程序+源码+LW文档)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

解决jeecgboot框架下单点登录后,页面刷新后,会自动跳转到单点登录服务的登录页问题

1.配置单点服务&#xff0c;可参考官方文档&#xff1a;http://doc.jeecg.com/2044170 - 打开.env文件,UE_APP_SSOtrue即代表开启SSO登录 NODE_ENVproduction VUE_APP_PLATFORM_NAMEJeecg-Boot 企业级快速开发平台 VUE_APP_SSOtrue- 打开.env.development文件&#xff0c;修改…

黑*头条_第7章_app端文章搜索(新版)

黑*头条_第7章_app端文章搜索(新版) 文章目录黑*头条_第7章_app端文章搜索(新版)1) 今日内容介绍1.1)App端搜索-效果图1.2)今日内容2) 搭建ElasticSearch环境2.1) 拉取镜像2.2) 创建容器2.3) 配置中文分词器 ik2.4) 使用postman测试3) app端文章搜索3.1) 需求分析3.2) 思路分析…

m基于CNN卷积神经网络的IBDFE单载波频域均衡算法

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 单载波频域均衡(SC-FDE)是解决符号间干扰(ISI)问题的一项重要技术。相比于单载波时域均衡(SC-TDE)技术和正交频分复用(OFDM)技术,SC-FDE技术具有复杂度低、峰均功率比小的优点。但是,SC-FDE技术中…

Android 测试文字编码格式

测试文字编码格式&#xff0c;与设置字符串格式 调用&#xff1a; juniversalchardet-1.0.3.jar app里的Build.gradle implementation files(libs\\juniversalchardet-1.0.3.jar) java调用&#xff1a; import org.mozilla.universalchardet.UniversalDetector;/*** 测试编…