LeetCode算法题7:DFS和BFS
文章目錄
- 前言
- 深度優(yōu)先搜索算法偽代碼:
- 廣度優(yōu)先搜索算法偽代碼:
- 一、圖像渲染
- DFS:
- BFS:
- 上面BFS算法存在的問題:
- 修改 1:
- 修改 2:
- 二、島嶼的最大面積
- DFS:
- BFS :
- 三、合并二叉樹
- DFS:
- BFS:
- 上面BFS算法存在的問題:
- 修改 1:
- 修改 2:
- 四、填充每個(gè)節(jié)點(diǎn)的下一個(gè)右側(cè)節(jié)點(diǎn)指針
- 思路 1:
- 樹的層序遍歷
- 思路 2:
- 五、01矩陣
- BFS:
- DP:
- 六、腐爛的橘子
- BFS:
- 總結(jié)
前言
??????Leetcode算法系列:https://leetcode-cn.com/study-plan/algorithms/?progress=njjhkd2
??????簡單總結(jié)一下DFS、BFS相關(guān)的算法題:
深度優(yōu)先搜索算法偽代碼:
??????一般需要建立一個(gè) visited 數(shù)組來標(biāo)記訪問過的元素。從一個(gè)節(jié)點(diǎn)開始,訪問它;找到它未被訪問過的后續(xù)節(jié)點(diǎn),遞歸執(zhí)行此算法。以下圖的二叉樹為例:訪問節(jié)點(diǎn)的順序依次為:A->B->C->D->E->F 類似于樹的先序遍歷。遍歷方式類似于先死磕一個(gè)方向打開缺口,再由缺口處擴(kuò)散來擴(kuò)大戰(zhàn)果,直至結(jié)束。
??????遞歸實(shí)現(xiàn)的偽代碼為:
廣度優(yōu)先搜索算法偽代碼:
??????此算法和樹的層序遍歷基本一致。一般需要建立一個(gè)輔助隊(duì)列。搜索方法為:一圈圈擴(kuò)散,從內(nèi)圈開始直到最外圍,隊(duì)列用來保持內(nèi)外圈的搜索順序(內(nèi)圈遍歷結(jié)束才能到外圈)。以上圖的二叉樹為例:訪問節(jié)點(diǎn)的順序依次為:A->B->E->C->D->F 類似于樹的層序遍歷。
??????遞歸實(shí)現(xiàn)的偽代碼 1:
??????遞歸實(shí)現(xiàn)的偽代碼 2:
function bfs(graph, root) {queue = [];queue.add([root]);visit(node);while (!queue.isEmpty()) node = queue.pop();for (node in node.children()) {queue.add(node);visit(node); }??????廣度優(yōu)先偽代碼 1 和 2 的差別后面在具體的問題中會(huì)講到,一般常采用偽代碼 2的寫法(即建議應(yīng)該采用入隊(duì)時(shí)訪問,并且在某些情況下在出隊(duì)時(shí)訪問會(huì)造成錯(cuò)誤)。
一、圖像渲染
??????題目鏈接:https://leetcode-cn.com/problems/flood-fill/
??????題目描述:有一幅以 m x n 的二維整數(shù)數(shù)組表示的圖畫 image ,其中 image[i][j] 表示該圖畫的像素值大小。
你也被給予三個(gè)整數(shù) sr , sc 和 newColor 。你應(yīng)該從像素 image[sr][sc] 開始對圖像進(jìn)行 上色填充 。
為了完成 上色工作 ,從初始像素開始,記錄初始坐標(biāo)的 上下左右四個(gè)方向上 像素值與初始坐標(biāo)相同的相連像素點(diǎn),接著再記錄這四個(gè)方向上符合條件的像素點(diǎn)與他們對應(yīng) 四個(gè)方向上 像素值與初始坐標(biāo)相同的相連像素點(diǎn),……,重復(fù)該過程。將所有有記錄的像素點(diǎn)的顏色值改為 newColor 。
最后返回 經(jīng)過上色渲染后的圖像 。
DFS:
??????其中 oldColor 表示整個(gè)二維數(shù)組中值為 oldColor 的元素需要被更改為 newColor 。**下面的算法沒有采用 visited 數(shù)組來標(biāo)記訪問過的元素,因?yàn)橐坏┌l(fā)現(xiàn)元素的值為 oldColor,就會(huì)改變它的值為 newColor,不會(huì)造成程序多次訪問同一個(gè)值為 oldColor 的元素,**也不會(huì)導(dǎo)致程序陷入循環(huán)。因此:參考上面的 DFS 模板,如果 visit() 方法不會(huì)對訪問的元素造成改變,那么就需要一個(gè) visited 數(shù)組來進(jìn)行標(biāo)記,否則如本題這樣不需要 visited 數(shù)組的存在。參考算法如下:
public int[][] floodFill(int[][] image, int sr, int sc, int newColor) {int oldColor=image[sr][sc];if(oldColor!=newColor)floodFill0(image,sr,sc,oldColor,newColor);return image;}public void floodFill0(int[][] image, int sr, int sc, int oldColor, int newColor) {if(sr<0||sc<0||sr>=image.length||sc>=image[0].length||image[sr][sc]!=oldColor)return; //退出條件為數(shù)組元素越界或碰到不符合要求的元素(元素值不等于 oldColor)image[sr][sc]=newColor;floodFill0(image,sr+1,sc,oldColor,newColor);floodFill0(image,sr-1,sc,oldColor,newColor);floodFill0(image,sr,sc+1,oldColor,newColor);floodFill0(image,sr,sc-1,oldColor,newColor);}??????稍微修改一下代碼,如下:
int[] x={1,0,-1,0};int[] y={0,1,0,-1};public int[][] floodFill(int[][] image, int sr, int sc, int newColor) {int oldColor=image[sr][sc];if(oldColor!=newColor)floodFill0(image,sr,sc,oldColor,newColor);return image;}public void floodFill0(int[][] image, int sr, int sc, int oldColor, int newColor) {if(sr<0||sc<0||sr>=image.length||sc>=image[0].length||image[sr][sc]!=oldColor)return;image[sr][sc]=newColor;for(int i=0;i<x.length;i++)floodFill0(image,sr+x[i],sc+y[i],oldColor,newColor);}BFS:
??????從起點(diǎn)開始一圈圈往外擴(kuò)散。參考算法如下:
public int[][] floodFill(int[][] image, int sr, int sc, int newColor) {int oldColor=image[sr][sc];if(oldColor!=newColor){int[] dx={1,0,-1,0};int[] dy={0,1,0,-1};Queue<int[]> queue=new LinkedList<>();queue.offer(new int[]{sr,sc});while(!queue.isEmpty()){int[] cell=queue.poll();int x=cell[0],y=cell[1];image[x][y]=newColor;for(int i=0;i<dx.length;i++){int mx=x+dx[i],my=y+dy[i];if(mx<0||my<0||mx>=image.length||my>=image[0].length||image[mx][my]!=oldColor){continue;}queue.offer(new int[]{mx,my});}}}return image;}上面BFS算法存在的問題:
??????按照本題描述中的示例,左上角 (0,0) 處的 1 會(huì)被添加進(jìn)隊(duì)列兩次,分別由(0,1) 處的 1 添加一次,(1,0) 處的 1 再添加一次。上述算法有可能導(dǎo)致同一個(gè)元素入隊(duì)多次,這是我們不希望看到的。而偽代碼 2 保證了 BFS 中每個(gè)元素只能入隊(duì)一次。
修改 1:
??????偽代碼 2:在每個(gè)元素入隊(duì)時(shí)改變它的值為 newColor。參考如下:
public int[][] floodFill(int[][] image, int sr, int sc, int newColor) {int oldColor=image[sr][sc];if(oldColor!=newColor){int[] dx={1,0,-1,0};int[] dy={0,1,0,-1};Queue<int[]> queue=new LinkedList<>();queue.offer(new int[]{sr,sc});image[sr][sc]=newColor;while(!queue.isEmpty()){int[] cell=queue.poll();int x=cell[0],y=cell[1];for(int i=0;i<dx.length;i++){int mx=x+dx[i],my=y+dy[i];if(mx<0||my<0||mx>=image.length||my>=image[0].length||image[mx][my]!=oldColor){continue;}queue.offer(new int[]{mx,my});image[mx][my]=newColor;}}}return image;}修改 2:
??????另一種方式即采用標(biāo)記數(shù)組,這樣需要在每次入隊(duì)時(shí)標(biāo)記元素為 true,修改如下:
public int[][] floodFill(int[][] image, int sr, int sc, int newColor) {boolean[][] visited=new boolean[image.length][image[0].length];int oldColor=image[sr][sc];if(oldColor!=newColor){int[] dx={1,0,-1,0};int[] dy={0,1,0,-1};Queue<int[]> queue=new LinkedList<>();queue.offer(new int[]{sr,sc});visited[sr][sc]=true;while(!queue.isEmpty()){int[] cell=queue.poll();int x=cell[0],y=cell[1];image[x][y]=newColor;for(int i=0;i<dx.length;i++){int mx=x+dx[i],my=y+dy[i];if(mx<0||my<0||mx>=image.length||my>=image[0].length||image[mx][my]!=oldColor||visited[mx][my]==true){continue;}queue.offer(new int[]{mx,my});visited[mx][my]=true;}}}return image;}??????為了防止BFS中同一個(gè)元素多次入隊(duì)的情況產(chǎn)生,需要在每個(gè)元素入隊(duì)時(shí)采用操作(比如修改,單純的訪問是不行的),其實(shí)更通用的方式是采用標(biāo)記數(shù)組來實(shí)現(xiàn)。即像 DFS 那樣。
二、島嶼的最大面積
??????題目鏈接:https://leetcode-cn.com/problems/max-area-of-island/
??????題目描述:給你一個(gè)大小為 m x n 的二進(jìn)制矩陣 grid 。
島嶼 是由一些相鄰的 1 (代表土地) 構(gòu)成的組合,這里的「相鄰」要求兩個(gè) 1 必須在 水平或者豎直的四個(gè)方向上 相鄰。你可以假設(shè) grid 的四個(gè)邊緣都被 0(代表水)包圍著。
島嶼的面積是島上值為 1 的單元格的數(shù)目。
計(jì)算并返回 grid 中最大的島嶼面積。如果沒有島嶼,則返回面積為 0 。
DFS:
??????本題不會(huì)對訪問的元素造成改變,所以需要 visited 數(shù)組來標(biāo)記是否訪問過該元素。參考代碼如下:(當(dāng)然也可以仿照上一題修改訪問過元素的值,比如將訪問過的 1 均變?yōu)?2,這樣就不需要使用 visited 數(shù)組了。缺點(diǎn)是會(huì)對原來的數(shù)組狀態(tài)造成破環(huán)。)
int count=0;int[] x={1,0,0,-1};int[] y={0,1,-1,0};public int maxAreaOfIsland(int[][] grid) {int re=0,m=grid.length,n=grid[0].length;boolean[][] visited=new boolean[m][n];for(int i=0;i<m;i++){for(int j=0;j<n;j++){if(grid[i][j]==1&&visited[i][j]==false){//找到每一個(gè)未被訪問過的島嶼。計(jì)算并比較其面積count=0;DFS(grid,i,j,visited);if(count>re)re=count;}}}return re;}public void DFS(int[][] grid,int i,int j,boolean[][] visited){if(i<0||j<0||i>=grid.length||j>=grid[0].length||grid[i][j]!=1||visited[i][j]==true)return;visited[i][j]=true;count++;for(int k=0;k<x.length;k++)DFS(grid,i+x[k],j+y[k],visited);}??????不帶有 visited 數(shù)組的 DFS 如下:
int count=0;int[] x={1,0,0,-1};int[] y={0,1,-1,0};public int maxAreaOfIsland(int[][] grid) {int re=0,m=grid.length,n=grid[0].length;int newValue=2;//用 2 來表示已經(jīng)探查過的島嶼。for(int i=0;i<m;i++){for(int j=0;j<n;j++){if(grid[i][j]==1){count=0;DFS(grid,i,j,newValue);if(count>re)re=count;}}}return re;}public void DFS(int[][] grid,int i,int j,int newValue){if(i<0||j<0||i>=grid.length||j>=grid[0].length||grid[i][j]!=1)return;grid[i][j]=newValue;count++;for(int k=0;k<x.length;k++)DFS(grid,i+x[k],j+y[k],newValue);}BFS :
??????和上一題類似,需要在元素入隊(duì)的時(shí)候進(jìn)行標(biāo)記或修改操作(比如將值也置為 2 )來防止同一個(gè)元素多次入隊(duì);否則采用偽代碼 1 的解法會(huì)導(dǎo)致同一個(gè)元素多次入隊(duì),會(huì)造成多次計(jì)數(shù),這樣就不能得到正確結(jié)果了。
public int maxAreaOfIsland(int[][] grid) {int count=0;int[] x={1,0,0,-1};int[] y={0,1,-1,0};int re=0,m=grid.length,n=grid[0].length;boolean[][] visited=new boolean[m][n];Queue<int[]> queue=new LinkedList<>();for(int i=0;i<m;i++){for(int j=0;j<n;j++){if(grid[i][j]==1&&visited[i][j]==false){count=0;queue.offer(new int[]{i,j});visited[i][j]=true;while(!queue.isEmpty()){int[] tmp=queue.poll();count++;for(int k=0;k<x.length;k++){int newX=tmp[0]+x[k],newY=tmp[1]+y[k];if(newX<0||newY<0||newX>=m||newY>=n||grid[newX][newY]!=1||visited[newX][newY]==true) continue;queue.offer(new int[]{newX,newY});visited[newX][newY]=true;}}if(count>re)re=count;}}}return re;}三、合并二叉樹
??????題目鏈接:https://leetcode-cn.com/problems/merge-two-binary-trees/
??????題目描述:給你兩棵二叉樹: root1 和 root2 。
想象一下,當(dāng)你將其中一棵覆蓋到另一棵之上時(shí),兩棵樹上的一些節(jié)點(diǎn)將會(huì)重疊(而另一些不會(huì))。你需要將這兩棵樹合并成一棵新二叉樹。合并的規(guī)則是:如果兩個(gè)節(jié)點(diǎn)重疊,那么將這兩個(gè)節(jié)點(diǎn)的值相加作為合并后節(jié)點(diǎn)的新值;否則,不為 null 的節(jié)點(diǎn)將直接作為新二叉樹的節(jié)點(diǎn)。
返回合并后的二叉樹。
注意: 合并過程必須從兩個(gè)樹的根節(jié)點(diǎn)開始。
DFS:
??????參考先序遍歷,先計(jì)算當(dāng)前節(jié)點(diǎn)的最終值,在遞歸進(jìn)行左右孩子節(jié)點(diǎn)值的判斷,在這里需要分情況計(jì)算的。參考算法如下(這個(gè)算法完全重建了一個(gè)新的樹):
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {if(root1!=null&&root2!=null)return new TreeNode(root1.val+root2.val,mergeTrees(root1.left,root2.left),mergeTrees(root1.right,root2.right));else if(root1==null&&root2!=null)return new TreeNode(root2.val,mergeTrees(null,root2.left),mergeTrees(null,root2.right));else if(root2==null&&root1!=null)return new TreeNode(root1.val,mergeTrees(root1.left,null),mergeTrees(root1.right,null));elsereturn null;}??????不完全重建一個(gè)新的樹的算法如下:
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {if(root1!=null&&root2!=null)return new TreeNode(root1.val+root2.val,mergeTrees(root1.left,root2.left),mergeTrees(root1.right,root2.right));else if(root1==null&&root2!=null)return root2;else if(root2==null&&root1!=null)return root1;elsereturn null;}??????或者下面這種,思路更加清晰一些,更加像樹的先序遍歷。
public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {if (t1 == null) {return t2;}if (t2 == null) {return t1;}TreeNode merged = new TreeNode(t1.val + t2.val);merged.left = mergeTrees(t1.left, t2.left);merged.right = mergeTrees(t1.right, t2.right);return merged;}BFS:
??????有兩個(gè)不算問題的問題:1,兩個(gè)數(shù)中的節(jié)點(diǎn)入隊(duì),如何保持相應(yīng)節(jié)點(diǎn)的一致性?2,假設(shè)已經(jīng)得到了最終樹的層序遍歷,如何恢復(fù)出一個(gè)樹?但其實(shí)問題并沒有想象中那么復(fù)雜,關(guān)鍵在于元素出隊(duì)之后要對此元素的左右孩子做判斷,來得到該位置上的最終節(jié)點(diǎn)。在這里只關(guān)心相應(yīng)的兩個(gè)節(jié)點(diǎn)均不為空的情形,若任意一個(gè)節(jié)點(diǎn)為空,新節(jié)點(diǎn)置為另一個(gè)不為空的節(jié)點(diǎn)。另外也不需要從層序遍歷恢復(fù)出一個(gè)樹,最終的目標(biāo)樹結(jié)構(gòu)在每一次元素出隊(duì)時(shí)都會(huì)建立一部分,直至結(jié)束。參考算法如下:()
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {LinkedList<TreeNode> q1=new LinkedList<>();LinkedList<TreeNode> q2=new LinkedList<>();LinkedList<TreeNode> q=new LinkedList<>();if(root1!=null&&root2!=null)q.offer(new TreeNode(root1.val+root2.val));else if(root1!=null)return root1;else if(root2!=null)return root2;else return null;TreeNode re=q.peek();q1.offer(root1);q2.offer(root2);while(!q1.isEmpty()){//這里的循環(huán)條件只要 q1 或 q2 不為空即可。TreeNode a=q1.poll(),b=q2.poll(),c=q.poll();TreeNode al=a.left,ar=a.right,bl=b.left,br=b.right;if(al!=null&&bl!=null){TreeNode tmp=new TreeNode(al.val+bl.val);c.left=tmp;q.offer(tmp);q1.offer(al);q2.offer(bl);}else if(al!=null)c.left=al;else if(bl!=null)c.left=bl;if(ar!=null&&br!=null){TreeNode tmp=new TreeNode(ar.val+br.val);c.right=tmp;q.offer(c.right);q1.offer(ar);q2.offer(br);}else if(ar!=null)c.right=ar;else if(br!=null)c.right=br;}return re;}上面BFS算法存在的問題:
??????按照本題描述中的示例,左上角 (0,0) 處的 1 會(huì)被添加進(jìn)隊(duì)列兩次,分別由(0,1) 處的 1 添加一次,(1,0) 處的 1 再添加一次。上述算法有可能導(dǎo)致同一個(gè)元素入隊(duì)多次,這是我們不希望看到的。而偽代碼 2 保證了 BFS 一定要保證一個(gè)元素只能入隊(duì)一次。
修改 1:
??????如果采用標(biāo)記數(shù)組的話,需要在每次入隊(duì)時(shí)標(biāo)記元素為 true,修改如下:
public int[][] floodFill(int[][] image, int sr, int sc, int newColor) {boolean[][] visited=new boolean[image.length][image[0].length];int oldColor=image[sr][sc];if(oldColor!=newColor){int[] dx={1,0,-1,0};int[] dy={0,1,0,-1};Queue<int[]> queue=new LinkedList<>();queue.offer(new int[]{sr,sc});visited[sr][sc]=true;while(!queue.isEmpty()){int[] cell=queue.poll();int x=cell[0],y=cell[1];image[x][y]=newColor;for(int i=0;i<dx.length;i++){int mx=x+dx[i],my=y+dy[i];if(mx<0||my<0||mx>=image.length||my>=image[0].length||image[mx][my]!=oldColor||visited[mx][my]==true){continue;}queue.offer(new int[]{mx,my});visited[mx][my]=true;}}}return image;}修改 2:
??????本題另一種更好的修改方式為:在每個(gè)元素入隊(duì)時(shí)改變它的值為 newColor,這樣在功能上可以做到和標(biāo)記數(shù)組一樣的效果。參考如下:
public int[][] floodFill(int[][] image, int sr, int sc, int newColor) {int oldColor=image[sr][sc];if(oldColor!=newColor){int[] dx={1,0,-1,0};int[] dy={0,1,0,-1};Queue<int[]> queue=new LinkedList<>();queue.offer(new int[]{sr,sc});image[sr][sc]=newColor;while(!queue.isEmpty()){int[] cell=queue.poll();int x=cell[0],y=cell[1];for(int i=0;i<dx.length;i++){int mx=x+dx[i],my=y+dy[i];if(mx<0||my<0||mx>=image.length||my>=image[0].length||image[mx][my]!=oldColor){continue;}queue.offer(new int[]{mx,my});image[mx][my]=newColor;}}}return image;}??????為了防止BFS中同一個(gè)元素多次入隊(duì)的情況產(chǎn)生,需要在每個(gè)元素入隊(duì)時(shí)采用操作(采用標(biāo)記數(shù)組或別的方式),DFS 不會(huì)存在這樣的問題,類似于先序遍歷,它是先對元素進(jìn)行操作,然后進(jìn)行遞歸調(diào)用的。
四、填充每個(gè)節(jié)點(diǎn)的下一個(gè)右側(cè)節(jié)點(diǎn)指針
??????題目鏈接:https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node/
??????題目描述:給定一個(gè) 完美二叉樹 ,其所有葉子節(jié)點(diǎn)都在同一層,每個(gè)父節(jié)點(diǎn)都有兩個(gè)子節(jié)點(diǎn)。二叉樹定義如下:
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
填充它的每個(gè) next 指針,讓這個(gè)指針指向其下一個(gè)右側(cè)節(jié)點(diǎn)。如果找不到下一個(gè)右側(cè)節(jié)點(diǎn),則將 next 指針設(shè)置為 NULL。
初始狀態(tài)下,所有 next 指針都被設(shè)置為 NULL。
思路 1:
樹的層序遍歷
??????在此給出一段代碼:用來分層打印出二叉樹結(jié)點(diǎn)信息。如何分層是通過兩個(gè)輔助變量做到的,其中 remainingNode 表示當(dāng)前層還未打印的結(jié)點(diǎn)數(shù)量,nextLayerN 表示下一層的結(jié)點(diǎn)數(shù)量。
public void printTree(){//分層打印樹節(jié)點(diǎn)。if(root==null)return;ArrayDeque<BinaryNode<T>> queue=new ArrayDeque<>();queue.offerLast(root);int remainingNode=1,nextLayerN=0;while(!queue.isEmpty()){BinaryNode<T> tmp=queue.pollFirst();System.out.print(tmp.element+" ");remainingNode--;if(tmp.left!=null){queue.offerLast(tmp.left);nextLayerN++;}if(tmp.right!=null){queue.offerLast(tmp.right);nextLayerN++;}if(remainingNode==0){System.out.println();remainingNode=nextLayerN;nextLayerN=0;}}}??????參考上面樹的分層打印,可得到算法如下:
public Node connect(Node root) {if(root==null)return root;LinkedList<Node> q=new LinkedList<>();q.offer(root);int remain=1,nextLayer=0;while(!q.isEmpty()){Node tmp=q.poll();remain--;if(remain>=1)//所有最右側(cè)結(jié)點(diǎn)的 next 值在初始化時(shí)都為 nulltmp.next=q.peek();if(tmp.left!=null){q.offer(tmp.left);nextLayer++;}if(tmp.right!=null){q.offer(tmp.right);nextLayer++;}if(remain==0){remain=nextLayer;nextLayer=0;}}return root;}思路 2:
??????在該完全二叉樹上,如果一個(gè)結(jié)點(diǎn)(node)存在左右結(jié)點(diǎn),那么直接令 node.left.next=node.right 即可,那么 node.right.next 的值呢?如果右邊沒有結(jié)點(diǎn),該值為 null;如果右邊存在結(jié)點(diǎn),那么 node.right.next=node.next.left;注意:在這里我們都是在上一層構(gòu)建當(dāng)前層的 next 關(guān)系的。可參考算法:
public Node connect(Node root) {if(root==null)return root;Node beforeL=root,tmp;//保存上一層的最左側(cè)結(jié)點(diǎn)while(beforeL.left!=null){tmp=beforeL;while(tmp!=null){//構(gòu)建當(dāng)前層的 next 指針關(guān)系tmp.left.next=tmp.right;if(tmp.next!=null)tmp.right.next=tmp.next.left;tmp=tmp.next;}beforeL=beforeL.left;}return root;}五、01矩陣
??????題目鏈接:https://leetcode-cn.com/problems/01-matrix/
??????題目描述:給定一個(gè)由 0 和 1 組成的矩陣 mat ,請輸出一個(gè)大小相同的矩陣,其中每一個(gè)格子是 mat 中對應(yīng)位置元素到最近的 0 的距離。
兩個(gè)相鄰元素間的距離為 1 。
BFS:
??????一般來講,BFS 算法可以參考樹的層序遍歷算法(分層遍歷),之前講過,BFS 可以看作是一圈圈擴(kuò)散的,在本題中,初始值(初始入隊(duì)值的數(shù)量)并非只有 1 個(gè),而是 n 個(gè) 0,但是這 n 個(gè) 0 可以抽象為一個(gè),稱為超級源點(diǎn)。
??????因?yàn)樗嵌鄠€(gè)源點(diǎn)的抽象,對它的處理方法和只有一個(gè)源點(diǎn)的方法沒有本質(zhì)上的區(qū)別。然后需要將這些 0 周圍距離為 1 的點(diǎn)設(shè)置為 1,將這些 1 周圍距離為 1 的點(diǎn)設(shè)置為 2 ,以此類推。
??????需要注意 BFS 一定需要防止同一個(gè)元素多次入隊(duì),用標(biāo)記數(shù)組的話需要在入隊(duì)時(shí)更改元素標(biāo)記,而不是在出隊(duì)時(shí)。參考算法如下:
public int[][] updateMatrix(int[][] mat) {if(mat==null||mat.length==0||mat[0].length==0)return new int[0][0];int m=mat.length,n=mat[0].length;LinkedList<int[]> queue=new LinkedList<>();boolean[][] flag=new boolean[m][n];//用來防止重復(fù)入隊(duì)int numCurr=0,numNext=0,valueCurr=0;for(int i=0;i<m;i++)//將超級源點(diǎn)入隊(duì)(即所有為 0 的點(diǎn))for(int j=0;j<n;j++)if(mat[i][j]==0){queue.add(new int[]{i,j});flag[i][j]=true;numCurr++;}int[] tmp;int i,j; while(!queue.isEmpty()){tmp=queue.poll();numCurr--;i=tmp[0];j=tmp[1];if(j+1<n&&flag[i][j+1]==false){queue.add(new int[]{i,j+1});flag[i][j+1]=true;numNext++;}if(j-1>=0&&flag[i][j-1]==false){queue.add(new int[]{i,j-1});flag[i][j-1]=true;numNext++;}if(i+1<m&&flag[i+1][j]==false){queue.add(new int[]{i+1,j});flag[i+1][j]=true;numNext++;}if(i-1>=0&&flag[i-1][j]==false){queue.add(new int[]{i-1,j});flag[i-1][j]=true;numNext++;}mat[i][j]=valueCurr;if(numCurr==0){valueCurr++;numCurr=numNext;numNext=0;}}return mat;}??????上面代碼是在出隊(duì)的時(shí)候重新設(shè)置 mat 數(shù)組的值,為了和入隊(duì)時(shí)設(shè)置 flag 統(tǒng)一,將其也放在入隊(duì)的時(shí)候。順便也換一種寫法,如下:
public int[][] updateMatrix(int[][] mat) {if(mat==null||mat.length==0||mat[0].length==0)return new int[0][0];int[] x={1,0,0,-1},y={0,1,-1,0};int m=mat.length,n=mat[0].length;LinkedList<int[]> queue=new LinkedList<>();boolean[][] flag=new boolean[m][n];int numCurr=0,numNext=0;for(int i=0;i<m;i++)for(int j=0;j<n;j++)if(mat[i][j]==0){queue.add(new int[]{i,j});flag[i][j]=true;numCurr++;}int[] tmp;int i,j; while(!queue.isEmpty()){tmp=queue.poll();numCurr--;i=tmp[0];j=tmp[1];for(int k=0;k<4;k++){int newI=i+x[k],newJ=j+y[k];if(newI>=0&&newI<m&&newJ>=0&&newJ<n&&flag[newI][newJ]==false){queue.add(new int[]{newI,newJ});numNext++;flag[newI][newJ]=true;mat[newI][newJ]=mat[i][j]+1;}}if(numCurr==0){numCurr=numNext;numNext=0;}}return mat;}DP:
??????動(dòng)態(tài)規(guī)劃的做法是請參考題解。它不需要從所有為 0 的點(diǎn)處開始計(jì)算,而是取決于狀態(tài)轉(zhuǎn)移:dp[i][j]=Math.min(dp[i][j],dp[i-1][j]+1,dp[i+1][j]+1,dp[i][j-1]+1,dp[i][j+1]+1),其中 dp[i][j]表示在(i,j)處的元素距離最近的 0 處的距離,即任意一個(gè)位置的距離是它本身和周圍四個(gè)格子的距離加一之間的最小值。實(shí)現(xiàn)的算法比較巧妙,第一次遍歷實(shí)現(xiàn)一半的狀態(tài)轉(zhuǎn)移(從任意位置的左上方開始),另一次遍歷實(shí)現(xiàn)另一半的轉(zhuǎn)臺轉(zhuǎn)移(從右下方開始),從而得到最終結(jié)果。參考算法如下:
public int[][] updateMatrix(int[][] mat) {int i,j,m=mat.length,n=mat[0].length; int[][] dp=new int[m][n];for(i=0;i<m;i++) //初始化 dp 數(shù)組,for(j=0;j<n;j++){if(mat[i][j]!=0)dp[i][j]=Integer.MAX_VALUE/2;}for(i=0;i<m;i++) //從左上方開始遍歷for(j=0;j<n;j++){if(i-1>=0)dp[i][j]=Math.min(dp[i][j],dp[i-1][j]+1);if(j-1>=0)dp[i][j]=Math.min(dp[i][j],dp[i][j-1]+1);}for(i=m-1;i>=0;i--) //從左上方開始遍歷for(j=n-1;j>=0;j--){if(i+1<m)dp[i][j]=Math.min(dp[i][j],dp[i+1][j]+1);if(j+1<n)dp[i][j]=Math.min(dp[i][j],dp[i][j+1]+1);}return dp;}六、腐爛的橘子
??????題目鏈接:https://leetcode-cn.com/problems/rotting-oranges/
??????題目描述:在給定的 m x n 網(wǎng)格 grid 中,每個(gè)單元格可以有以下三個(gè)值之一:
值 0 代表空單元格;
值 1 代表新鮮橘子;
值 2 代表腐爛的橘子。
每分鐘,腐爛的橘子 周圍 4 個(gè)方向上相鄰 的新鮮橘子都會(huì)腐爛。
返回 直到單元格中沒有新鮮橘子為止所必須經(jīng)過的最小分鐘數(shù)。如果不可能,返回 -1 。
BFS:
??????這道題很適合用 BFS,因?yàn)閺囊蝗Φ搅硪蝗?#xff0c;剛好 time 加一。所以采用超級源點(diǎn)的 BFS,在入隊(duì)的時(shí)候更改相應(yīng)的 grid 值。參考算法如下:
public int orangesRotting(int[][] grid) {int[] xa={0,1,0,-1};int[] ya={1,0,-1,0};int m=grid.length,n=grid[0].length;int[][] store=new int[m][n];LinkedList<int[]> queue=new LinkedList<>();int currT=0,num1=0;for(int i=0;i<m;i++)for(int j=0;j<n;j++){if(grid[i][j]==2){queue.add(new int[]{i,j});currT++;}else if(grid[i][j]==1)num1++;}if(num1==0) //特殊情況。return 0; int time=-1,x,y,nextT=0,xx,yy;int[] tmp;while(!queue.isEmpty()){tmp=queue.poll();x=tmp[0];y=tmp[1];currT--;for(int i=0;i<4;i++){xx=x+xa[i];yy=y+ya[i];if(xx>=0&&xx<m&&yy>=0&&yy<n&&grid[xx][yy]==1){grid[xx][yy]=2;queue.add(new int[]{xx,yy});nextT++;num1--;}}if(currT==0){time++;currT=nextT;nextT=0;}}if(num1!=0)return -1;return time;}總結(jié)
??????完。
總結(jié)
以上是生活随笔為你收集整理的LeetCode算法题7:DFS和BFS的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: LeetCode算法题6:滑动窗口*
- 下一篇: LeetCode算法题8:递归和回溯1