先說明一下:本體正確解法使用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; }