算法基础——广度优先搜索 / 深度优先搜索(三)

一、二进制矩阵中的最短路径

  • bfs最短路径

  • 思路:从 0, 0 这个点出发,将周围点入队,再从队中拿出这些点并将其周围点入队,也就是一圈圈的寻找

  • 可以理解为一圈圈的话,每一圈就代表一步

  • 注意点:不能重复访问单元格

    • 开始:将 0, 0 入队,并标记被访问,第一步记录
    • 符合遍历条件:不越界且等于0
    • 然后入队
    • 每次外循环确保队列不空
      • 内循环,将开始循环前队列的元素个数记录下来
      • 然后逐一出队,直到记录为0
      • 出队后处理:将这个点的八个方向都确定是否可以入队:
        • 不越界、未访问、值为0
        • 入队、标记被访问
      • 当这一圈的遍历中出现(n - 1, n - 1))这个点,那么路径已经可以确定是走完了,直接返回步数即可
  • class Solution {
        //更改八个方向的数组:右,右下,下,左下,左,左上,上,右上
        private int[] X = {0,1,1,1,0,-1,-1,-1};//行
        private int[] Y = {1,1,0,-1,-1,-1,0,1};//列
        private int m,n;
        public int shortestPathBinaryMatrix(int[][] grid) {
            m = grid.length; n = grid[0].length;
            if(grid[0][0] == 1 || grid[m-1][n-1] == 1) return -1;//特殊情况特殊考虑,出口和入口被堵死
            if(m == 1 && grid[0][0] == 0) return 1;//只有一个0的情况
            int[][] memory = new int[m][n];//记忆数组,访问过的都记录一下
            Queue<int[]> a = new LinkedList();//队列,里面保存int数组为坐标
            a.add(new int[]{0, 0});//一开始将起点入队
            memory[0][0] = 1;//起点标记为已经访问
            int step = 1;//理论上的初值
            while (!a.isEmpty()){
                int s = a.size();//为step叠加用,用于将初始队列中的元素都取出来
                for (int i = 0; i < s; i++){
                    int[] temp = a.poll();//poll()方法队首元素出队
                    int x = temp[0];
                    int y = temp[1];
                    for(int j = 0; j < 8; j++){
                        int x1 = x + X[j];
                        int y1 = y + Y[j];
                        if(x1 >= 0 && x1 < m && y1 >= 0 && y1 < n && grid[x1][y1] == 0 && memory[x1][y1] != 1){//数组不越界,值为1,//没有被访问过才能入队
                            a.add(new int[]{x1, y1});//入队
                            memory[x1][y1] = 1;//标记已访问!!!!!!!!!!!!不要忘记
                        }
                    }
                    if(x == m-1 && y == n-1) return step;//终止条件
                }
                step++;
            }
            return -1;//队列为空即为不能到达终点,返回-1
        }
    }
    

二、被围绕的区域

class Solution {
    public void solve(char[][] board) {
        //判断字符‘o’是否处于边界或与边界的字符‘O’相连,判否:将这批字符‘O’填充为‘X’
        for(int i = 0; i < board.length; i++) {
            for(int j = 0; j < board[0].length; j++) {
                if(board[i][j] == 'O') {
                    List<int[]> points = helper(i, j, board);
                    if(points !=null) {
                        while(!points.isEmpty()) {
                            int[] t = points.remove(0);
                            board[t[0]][t[1]] = 'X';
                        }
                    }
                }
            }
        }
    }

    //上,下,左,右
    private int[] xi = new int[] {-1,1,0,0};
    private int[] yj = new int[] {0,0,-1,1};
    private List<int[]> helper(int i, int j, char[][] board) {
        List<int[]> ans = new ArrayList<>();
        int[][] memory = new int[board.length][board[0].length];
        Queue<int[]> queue = new LinkedList<>();
        queue.add(new int[] {i, j});
        while(!queue.isEmpty()) {
            int size = queue.size();
            while(size > 0) {
                size--;
                int[] t = queue.poll();
                int ti = t[0];
                int tj = t[1];
                if(ti == 0 || ti == board.length - 1 || tj == 0 || tj == board[0].length - 1) {
                    return null;
                }
                ans.add(t);
                memory[ti][tj] = 1;
                for(int k = 0; k < 4; k++) {
                    int x = ti + xi[k];
                    int y = tj + yj[k];
                    if(x >= 0 && x < board.length && y >= 0 && y < board[0].length && memory[x][y] == 0 && board[x][y] == 'O') {
                        memory[x][y] = 1;
                        queue.add(new int[] {x, y});
                    }
                }
            }
        }
        return ans;
    }
}

分析题目发现:被围绕的字符‘O'都不会到达矩阵的边界上,所以遇到该字符后,利用bfs搜索确定被围绕的区域,然后改为字符’X‘即可

但思路反着来:

  1. 遍历矩阵边界,将与边界相连的字符’O'都改为‘#’

  2. 然后,遍历矩阵,将剩余‘O'填充为’X‘,’#‘填充为’O‘

  3. 实际上比不需要记忆数组了,因为可以通过修改原数组做标记

  4. class Solution {
        public void solve(char[][] board) {
            int rows = board.length, cols = board[0].length;
            // 从边界开始查找,转换
            for (int i = 0; i < rows; ++i) {//先找左右两列
                if (board[i][0] == 'O') bfs(board, i, 0);
                if (board[i][cols - 1] == 'O') bfs(board, i, cols - 1);
            }
            for (int i = 0; i < cols; ++i) {//找上下两行
                if (board[0][i] == 'O') bfs(board, 0, i);
                if (board[rows - 1][i] == 'O') bfs(board, rows - 1, i);
            }
            // 恢复转换 和 按题意设置 X元素
            for (int i = 0; i < rows; ++i) {
                for (int j = 0; j < cols; ++j) {
                    if (board[i][j] == 'O') board[i][j] = 'X';
                    if (board[i][j] == '#') board[i][j] = 'O';
                }
            }
        }
    
        private void bfs(char[][] board, int i, int j) {
            int m = board.length, n = board[0].length;
            Queue<int[]> queue = new LinkedList();
            queue.add(new int[] {i, j});
            while(!queue.isEmpty()) {
                int[] t = queue.poll();
                int ti = t[0];
                int tj = t[1];
                if(ti >= 0 && ti < m && tj >= 0 && tj < n && board[ti][tj] == 'O') {
                    board[ti][tj] = '#';
                    queue.add(new int[] {ti - 1, tj});
                    queue.add(new int[] {ti + 1, tj});
                    queue.add(new int[] {ti, tj - 1});
                    queue.add(new int[] {ti, tj + 1});
                }
            }
        }
    }
    

三、所有可能的路径

没看题的结果:

少看的条件:找出所有从节点0到节点n-1的路径并输出

误以为存在多个节点作为路径起点

考虑了如何多个起点的做法

class Solution {

    public List<List<Integer>> allPathsSourceTarget(int[][] graph) {
        int[] begin = new int[graph.length];//起点数组,为0的才可做为新起点
        List<List<Integer>> ans = new ArrayList<>();
        for(int i = 0; i < graph.length; i++) {
            if(begin[i] == 0) {//是起点
                begin[i] = 1;
                List<Integer> road = new ArrayList<>();
                road.add(i);
                for(int j = 0; j < graph[i].length; j++) {
                    begin[graph[i][j]] = 1;
                    findRoad(ans, graph, graph[i][j], road, begin);
                }
            }
        }
        return ans;
    }

    private void findRoad(List<List<Integer>> ans, int[][] graph, int index, List<Integer> road, int[] begin) {
        road = new ArrayList(road);
        road.add(index);
        if(graph[index].length == 0) {
            ans.add(road);
        } else {
            for(int k = 0; k < graph[index].length; k++) {
                begin[graph[index][k]] = 1;
                findRoad(ans, graph, graph[index][k], new ArrayList(road), begin);
            }
        }
    }
}
  • 正确的做法

    • 从节点 0 到节点 n-1 的路径:节点0,就是数组的第一行

    • 需要注意路径的添加与new

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

推荐阅读更多精彩内容