LeetCode1293网格中的最短路径(DFS和BFS)分析
Posted 程序杰杰
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode1293网格中的最短路径(DFS和BFS)分析相关的知识,希望对你有一定的参考价值。
先说明一下:本体正确解法使用BFS(广度优先),最下方的是关于BFS的思路
BFS与DFS最大的区别在于:
BFS首先搞同一层,然后从同一层一个个出发,找下一层
DFS首先搞一个,一直搞到底,然后再搞下一个,也就是回溯
接下来,我首先分析一下,我的整个思路的分析~~
==超时的个人思路:=
对于我个人来讲,看到题的第一反应是使用DFS(深度优先),很简单嘛,思路就是:
从0,0开始,可以往上下左右方向走,我只需要确认:1.我下面走的一步没有超出边界;2.下面的一步没有被走过即可
当走到了rowIndex-1,colIndex-1时,说明找到一条走法,将本次走过的长度加入到全局的ArrayList中,最后找出最小的那个就好啦。
当然本题中多出来一个k,代表我们可以破解边界的次数,这个就是当前如果是边界,且能破的话,ok那就破,继续和上方一样,否则就结束啦。
==end==
思路是不是非常清晰,ok,我们上代码
如果没有边界的话,最短的长度一定是row+col-2(因为0,0不算),(rowIndex-1,colIndex-1是我们需要到达的,因此一定不是1)那么当我们可以破接障碍物的次数>=row+col-3的时候,那么这个全局最短路径一定是可行的啦!
代码中 visited[rowIndex][colIndex] = true;后最下方又设置为false的原因,就是回溯的思想,本次操作完,还原,进行下一个节点的深度查找!
//有k次可以清除障碍物的机会 //每次走的时候我都选择一下,清除和不清楚,这样既有只要有障碍物就清除的,也有走到最后一个障碍物都没q清除的 public int shortestPath(int[][] grid, int k) { boolean[][] visited = new boolean[grid.length][grid[0].length]; if(k>=grid.length+grid[0].length-3)return grid.length+grid[0].length-2; findPath(grid, 0, 0, k, visited, 0); if (countList.size() == 0) return -1; int minV = countList.get(0); for (int i = 1; i < countList.size(); i++) { minV = minV < countList.get(i) ? minV : countList.get(i); } return minV; } //策略:下,右,左,上(不能搞策略,应该全部走一遍看看) //k代表还有几次清除的机会 ArrayList<Integer> countList = new ArrayList<>(); private void findPath(int[][] grid, int rowIndex, int colIndex, int k, boolean[][] visited, int count) { if (rowIndex == grid.length - 1 && colIndex == grid[0].length - 1) { countList.add(count); } if (rowIndex >= 0 && rowIndex < grid.length && colIndex >= 0 && colIndex < grid[0].length && !visited[rowIndex][colIndex]) { //破障碍 //当前存在障碍,只有有破障碍的机会的时候才能继续往下走 if (grid[rowIndex][colIndex] == 1) { if (k > 0) { visited[rowIndex][colIndex] = true; findPath(grid, rowIndex - 1, colIndex, k - 1, visited, count + 1);//上 findPath(grid, rowIndex + 1, colIndex, k - 1, visited, count + 1);//下 findPath(grid, rowIndex, colIndex - 1, k - 1, visited, count + 1);//左 findPath(grid, rowIndex, colIndex + 1, k - 1, visited, count + 1);//右 visited[rowIndex][colIndex] = false; } } else {//如果当前不存在障碍,那么直接找 visited[rowIndex][colIndex] = true; findPath(grid, rowIndex - 1, colIndex, k, visited, count + 1);//上 findPath(grid, rowIndex + 1, colIndex, k, visited, count + 1);//下 findPath(grid, rowIndex, colIndex - 1, k, visited, count + 1);//左 findPath(grid, rowIndex, colIndex + 1, k, visited, count + 1);//右 visited[rowIndex][colIndex] = false; } } }
感觉写的没啥问题,但是很遗憾,超时啦,难受~
==BFS解法思路(可行)==
首先看到了BFS的模板,感觉还不错哦,贴出来,分析~
我们可以看到,首先初始化一个队列,当然广度优先需要一层的元素,因此我们最开始需要把第一个节点放进去
然后,每次进来的时候,遍历当前层所有节点,当然每遍历一个,我们就删除它,最后将本层全部遍历完,队列中留的就是下一层的全部
就这样逐层找,看看能否找到最后一层我们需要的节点,如果能则返回,如果都找了,就是找不到最后一层的某个节点,队列也空了,那么说明确实找不到了,ok,retrun false或者找不到就好啦。
// 节点访问标识,访问过的节点无需访问(剪枝) int[][] visited = new int[m][n]; // 队列初始化 Queue<Node> queue = new LinkedList(); // 【第1步】将起点加入队列, 非空进入循环 queue.add(第一个数据) while(!queue.isEmpty()) { // 【第2步】 获取当前队列长度即同一层级(辈分)节点个数,并遍历 int size = queue.size(); // 一定要先获取,queue后面要加入下一层级节点 for (int i = 0; i < size; i++) { // 【第3步】 对同一层级节点逐个寻找下一层有效**路径节点**,找到目标直接返回结果终止搜索。 Node node = queue.poll(); // 下一层节点 比如网格上下左右移动 Node nextNode = node.x + xj; // 1. 不符合要求的下一层节点直接过滤(比如越界、已经被visited[][]标记访问了) // 2. 找到目标节点 直接返回结果 // 3. 符合要求的下一层节点放入队列 queue.offer(nextNode) } } // 【第4步】 BFS搜索完成没找到结果,返回-1 return -1;
===BFS算法代码实现===
思路分析~
==start==
广度优先,需要牢牢记住的一个点是,因为是逐层出发的,因此当前如果被走过,要么是当前层其他节点走的,要么是前面的层走的,前面的层走过了,这个点对它来讲就没啥用了,如果是本层的其他节点走过了,因为处于同一层,他们走到该点的步数一定是一样的!
首先visited记录当前可以破解的剩余次数,如果当前节点没走的话,标识为-1
构造队列,这个是BFS的精髓,使用队列逐层向下找
从同一层节点一个个遍历,并弹出,找出从当前节点出发的路
当然需要满足1:不能超出边界;2.判断当前经过障碍物的数量不能大于k
如果当前已经达到了右下角,那么Ok答案在这里,因为是广度优先,当前一定是最近的。
如果当前节点被访问过,ok,按照上面红色的理解,我们只需要判别当前节点剩余可跨越障碍的数量是不是比现存的要大,如果大的话,我们替换为当前的障碍数,否则不需要
如果没访问过,那么直接写当前障碍数,ok啦!
==end==
创建一个类,用于记录当前位置和跨越的障碍数
class Point { int x; int y; int oneCount; public Point(int x, int y, int oneCount) { this.x = x; this.y = y; this.oneCount = oneCount; } }
public int shortestPathOK(int[][] grid, int k) { int row=grid.length,col=grid[0].length; if(k>=row+col-3)return row+col-2; int[][] visited=new int[row][col];//没走过则为-1 ,否则为当前可破解障碍的剩余次数 for (int i = 0; i < visited.length; i++) { for (int j = 0; j < visited[0].length; j++) { visited[i][j]=-1; } } visited[0][0]=k;//0,0点剩余k次破解障碍 Queue<Point> queue=new LinkedList<>(); Point point=new Point(0,0,0); queue.add(point); Point temp;//用于内部接收当前的队列元素 // 定义四个方向移动坐标 int[] dx = {1, -1, 0, 0}; int[] dy = {0, 0, 1, -1}; int moveNum=0; while (!queue.isEmpty()){ moveNum++;//因为是广度优先,移动一次,加一次就好 int size=queue.size();//当前层的个数 for(int i=0;i<size;i++){ temp=queue.poll(); //需要从当前节点往上下左右移动 for(int j=0;j<4;j++){ int xNew=dx[j]+temp.x; int yNew=dy[j]+temp.y; //不能越界 if(xNew<0||xNew>=row||yNew<0||yNew>=col){ continue; } int oneCountNew=temp.oneCount+grid[xNew][yNew]; if(xNew==row-1&&yNew==col-1){ return moveNum; } //当前是障碍物,但是已经没有穿越障碍物的机会了 if(grid[xNew][yNew]==1&&oneCountNew>k){ continue; } //如果当前被访问过,因为是BFS,说明访问次数是不大于当前的访问次数的,如果剩余可破解的障碍物数也不比他小,那当前走法一定不是一个好的走法 if(visited[xNew][yNew]!=-1&&visited[xNew][yNew]>=k-oneCountNew){ continue; } else { //这里为什么可以改呢? //因为这里是广度优先,因为从当前位置走只能上下左右,不能走到同一个位置上 //因此这里走过了,要么这个点在另一条路径上已经不在需要,要么他们走的步长是一样的 //如果不需要则我修改对原路径也没有任何影响 //如果步长一样,本次剩余跨障碍的次数多,当时这个好啦 visited[xNew][yNew]=k-oneCountNew; } queue.offer(new Point(xNew,yNew,oneCountNew)); } } } //没找到 return -1; }
以上是关于LeetCode1293网格中的最短路径(DFS和BFS)分析的主要内容,如果未能解决你的问题,请参考以下文章
[JavaScript 刷题] 搜索 - 计算在网格中从原点到特定点的最短路径长度 Leetcode 1091
LeetCode 0864. 获取所有钥匙的最短路径:广搜 + 状压