算法题总结(十九)——图论

图论

DFS框架

void dfs(参数) {
if (终止条件) {存放结果;return;
}for (选择:本节点所连接的其他节点) {处理节点;dfs(图,选择的节点); // 递归回溯,撤销处理结果
}
}

深搜三部曲

  1. 确认递归函数,参数
  2. 确认终止条件
  3. 处理目前搜索节点出发的路径

797、所有可能的路径

给你一个有 n 个节点的 有向无环图(DAG),请你找出所有从节点 0 到节点 n-1 的路径并输出(不要求按特定顺序

graph[i] 是一个从节点 i 可以访问的所有节点的列表(即从节点 i 到节点 graph[i][j]存在一条有向边)。

示例 1:

输入:graph = [[1,2],[3],[3],[]]
输出:[[0,1,3],[0,2,3]]
解释:有两条路径 0 -> 1 -> 3 和 0 -> 2 -> 3

1、确认递归函数、参数

首先我们dfs函数一定要存一个图,用来遍历的,还要存一个目前我们遍历的节点,定义为x。

至于路径和路径集合,可以放在全局变量里。

2、确认终止条件

当目前遍历的节点 为 最后一个节点的时候,就找到了一条,从 出发点到终止点的路径。

3、处理目前搜索节点出发的路径

class Solution {List<List<Integer>> res =new ArrayList<>();List<Integer> path=new ArrayList<>();public void DFS(int[][] graph,int x){//说明找到了终点if(x==graph.length-1){res.add(new ArrayList<>(path));  //这里一定要建立一个新的return;}//遍历和x相连的结点for(int i=0;i<graph[x].length;i++){path.add(graph[x][i]);DFS(graph,graph[x][i]);path.remove(path.size()-1);  //回溯}}public List<List<Integer>> allPathsSourceTarget(int[][] graph) {//从头结点开始path.add(0);DFS(graph,0);return res;}
}

BFS框架

广搜的搜索方式就适合于解决两个点之间的最短路径问题。因为广搜是从起点出发,以起始点为中心一圈一圈进行搜索,一旦遇到终点,记录之前走过的节点就是一条最短路。

当然,也有一些问题是广搜 和 深搜都可以解决的,例如岛屿问题,这类问题的特征就是不涉及具体的遍历方式,只要能把相邻且相同属性的节点标记上就行

200、岛屿数量

给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。

岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。

此外,你可以假设该网格的四条边均被水包围。

示例 1:

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

示例 2:

输入:grid = [["1","1","0","0","0"],["1","1","0","0","0"],["0","0","1","0","0"],["0","0","0","1","1"]
]
输出:3

思路

本题思路,遇到一个没有遍历过的节点陆地,计数器就加一,然后把该节点陆地所能遍历到的陆地都标记上。

在遇到标记过的陆地节点和海洋节点的时候直接跳过。 这样计数器就是最终岛屿的数量。

那么如何把节点陆地所能遍历到的陆地都标记上呢,就可以使用 DFS,BFS或者并查集

DFS:

class Solution {boolean[][] visited;  //定义成全局的,不用传递int dir[][]={{0,1},{0,-1},{1,0},{-1,0}};  //右、左、下、上//把与某陆地结点相邻的陆地都标记上public void DFS(char[][] grid,int x,int y){   if(visited[x][y]==true||grid[x][y]=='0') //如果被访问过或者是水return;visited[x][y]=true;for(int i=0;i<4;i++){ //四个方向遍历一遍int nx=x+dir[i][0];int ny=y+dir[i][1];if(nx<0||ny<0||nx>=grid.length||ny>=grid[0].length)continue;//也可以把上面的放这里,if(visited[x][y]==false && grid[x][y]=='1')DFS(grid,nx,ny);}}public int numIslands(char[][] grid) {int count=0;visited=new boolean[grid.length][grid[0].length];//遇到一个没有遍历过的节点陆地,计数器就加一//然后把该节点陆地所能遍历到的陆地都标记上。for(int i=0;i<grid.length;i++){for(int j=0;j<grid[0].length;j++){if(visited[i][j]==false && grid[i][j]=='1'){count++;DFS(grid,i,j);}}}return count;}
}

BFS

不少同学用广搜做这道题目的时候,超时了。 这里有一个广搜中很重要的细节:

根本原因是只要 加入队列就代表 走过,就需要标记,而不是从队列拿出来的时候再去标记走过

很多同学可能感觉这有区别吗?

如果从队列拿出节点,再去标记这个节点走过,就会发生下图所示的结果,会导致很多节点重复加入队列。

class Solution {boolean[][] visited;  //定义成全局的,不用传递int dir[][]={{0,1},{0,-1},{1,0},{-1,0}};  //右、左、下、上//把与某陆地结点相邻的陆地都标记上public void BFS(char[][] grid,int x,int y){   Deque<int[]> queue=new ArrayDeque<>();queue.offer(new int[]{x,y});visited[x][y]=true; //只要入队就标记while(!queue.isEmpty()){int[] cur=queue.poll();int cx=cur[0];int cy=cur[1];for(int i=0;i<4;i++){int nx=cx+dir[i][0];int ny=cy+dir[i][1];if(nx<0||ny<0||nx>=grid.length||ny>=grid[0].length)continue;if(visited[nx][ny]==false && grid[nx][ny]=='1'){   queue.offer(new int[]{nx,ny});visited[nx][ny]=true;}}}    }public int numIslands(char[][] grid) {int count=0;visited=new boolean[grid.length][grid[0].length];//遇到一个没有遍历过的节点陆地,计数器就加一//然后把该节点陆地所能遍历到的陆地都标记上。for(int i=0;i<grid.length;i++){for(int j=0;j<grid[0].length;j++){if(visited[i][j]==false && grid[i][j]=='1'){count++;BFS(grid,i,j);}}}return count;}
}

695、岛屿的最大面积

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

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

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

计算并返回 grid 中最大的岛屿面积。如果没有岛屿,则返回面积为 0 。

示例 1:

输入: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 。

DFS:

设置一个全局变量count,每次DFS的时候++,遍历的时候把count置为0

class Solution {boolean[][] visited;int[][] dir ={{0,1},{0,-1},{1,0},{-1,0}};int count;public void DFS(int[][] grid,int x,int y){   if(visited[x][y]==true ||grid[x][y]==0)return;visited[x][y]=true;count++;for(int i=0;i<4;i++){int nx=x+dir[i][0];int ny=y+dir[i][1];if(nx<0||ny<0||nx>=grid.length||ny>=grid[0].length)continue;DFS(grid,nx,ny);}}public int maxAreaOfIsland(int[][] grid) {visited=new boolean[grid.length][grid[0].length];int result=0;  //记录最大值for(int i=0;i<grid.length;i++){for(int j=0;j<grid[0].length;j++){if(visited[i][j]==false && grid[i][j]==1){count=0; //每次找的时候,赋值为0DFS(grid,i,j);result=Math.max(result,count);}}}return result;}
}

BFS

class Solution {boolean[][] visited;int[][] dir ={{0,1},{0,-1},{1,0},{-1,0}};int count;public void BFS(int[][] grid,int x,int y){   Deque<int[]> queue=new ArrayDeque<>();queue.offer(new int[]{x,y});visited[x][y]=true;count++;while(!queue.isEmpty()){int [] cur=queue.poll();int cx=cur[0];int cy=cur[1];for(int i=0;i<4;i++){int nx=cx+dir[i][0];int ny=cy+dir[i][1];if(nx<0||ny<0||nx>=grid.length||ny>=grid[0].length)continue;if(visited[nx][ny]==false && grid[nx][ny]==1){queue.offer(new int[]{nx,ny});visited[nx][ny]=true;count++;}}}     }public int maxAreaOfIsland(int[][] grid) {visited=new boolean[grid.length][grid[0].length];int result=0;  //记录最大值for(int i=0;i<grid.length;i++){for(int j=0;j<grid[0].length;j++){if(visited[i][j]==false && grid[i][j]==1){count=0; //每次找的时候,赋值为0BFS(grid,i,j);result=Math.max(result,count);}}}return result;}
}

1020、飞地的数量

给你一个大小为 m x n 的二进制矩阵 grid ,其中 0 表示一个海洋单元格、1 表示一个陆地单元格。

一次 移动 是指从一个陆地单元格走到另一个相邻(上、下、左、右)的陆地单元格或跨过 grid 的边界。

返回网格中 无法 在任意次数的移动中离开网格边界的陆地单元格的数量。

示例 1:

输入:grid = [[0,0,0,0],[1,0,1,0],[0,1,1,0],[0,0,0,0]]
输出:3
解释:有三个 1 被 0 包围。一个 1 没有被包围,因为它在边界上。

示例 2:

输入:grid = [[0,1,1,0],[0,0,1,0],[0,0,1,0],[0,0,0,0]]
输出:0
解释:所有 1 都在边界上或可以到达边界。

思路:

本题要求找到不靠边的陆地面积,那么我们只要从周边找到陆地然后 通过 dfs或者bfs 将周边靠陆地且相邻的陆地都变成海洋,然后再去重新遍历地图的时候,统计此时还剩下的陆地就可以了,所以不需要使用visited数组。

DFS:

class Solution {int[][] dir={{0,1},{0,-1},{1,0},{-1,0}};public void DFS(int[][] grid,int x,int y){if(grid[x][y]==0)return;grid[x][y]=0;   //访问一个陆地,就把他变成海洋for(int i=0;i<4;i++){int nx=x+dir[i][0];int ny=y+dir[i][1];if(nx<0||ny<0||nx>=grid.length||ny>=grid[0].length)continue;DFS(grid,nx,ny);}}public int numEnclaves(int[][] grid) {int count=0;//遍历边界,如果边界是陆地,就使用dfs把相邻的变成海洋for(int i=0;i<grid.length;i++){if(grid[i][0]==1)DFS(grid,i,0);if(grid[i][grid[0].length-1]==1)DFS(grid,i,grid[0].length-1);}for(int j=1;j<grid[0].length-1;j++){if(grid[0][j]==1)DFS(grid,0,j);if(grid[grid.length-1][j]==1)DFS(grid,grid.length-1,j);}//计算陆地单元格的数量for(int i=0;i<grid.length;i++){for(int j=0;j<grid[0].length;j++){if(grid[i][j]==1)count++;}}return count;}
}

BFS:

class Solution {int[][] dir={{0,1},{0,-1},{1,0},{-1,0}};public void BFS(int[][] grid,int x,int y){Queue<int[]> queue =new LinkedList<>();queue.offer(new int[]{x,y});grid[x][y]=0;// 把陆地变为海洋while(!queue.isEmpty()){int[] cur=queue.poll();int cx=cur[0];int cy=cur[1];for(int i=0;i<4;i++){int nx=cx+dir[i][0];int ny=cy+dir[i][1];if(nx<0||ny<0||nx>=grid.length||ny>=grid[0].length)continue;if(grid[nx][ny]==1){queue.offer(new int[]{nx,ny});grid[nx][ny]=0;   // 把陆地变为海洋}} }}public int numEnclaves(int[][] grid) {int count=0;//遍历边界,如果边界是陆地,就使用dfs把相邻的变成海洋for(int i=0;i<grid.length;i++){if(grid[i][0]==1)BFS(grid,i,0);if(grid[i][grid[0].length-1]==1)BFS(grid,i,grid[0].length-1);}for(int j=1;j<grid[0].length-1;j++){if(grid[0][j]==1)BFS(grid,0,j);if(grid[grid.length-1][j]==1)BFS(grid,grid.length-1,j);}//计算陆地单元格的数量for(int i=0;i<grid.length;i++){for(int j=0;j<grid[0].length;j++){if(grid[i][j]==1)count++;}}return count;}
}

130、被围绕的区域

给你一个 m x n 的矩阵 board ,由若干字符 ‘X’ 和 ‘O’ ,找到所有被 ‘X’ 围绕的区域,并将这些区域里所有的 ‘O’ 用 ‘X’ 填充。

示例 1:

输入:board = [["X","X","X","X"],["X","O","O","X"],["X","X","O","X"],["X","O","X","X"]]
输出:[["X","X","X","X"],["X","X","X","X"],["X","X","X","X"],["X","O","X","X"]]
解释:被围绕的区间不会存在于边界上,换句话说,任何边界上的 'O' 都不会被填充为 'X'。 任何不在边界上,或不与边界上的 'O' 相连的 'O' 最终都会被填充为 'X'。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。

被围绕的区域就是出不去的区域。

与上一题的思路类似,任何边界上的O以及与边界O相邻的O都不会变动,其他的O都会被填充为X,因此先对边界进行遍历,把这些与边界O以及边界相邻的O都标记为A ,然后遍历整个数组,把O变为X,把A变为O

DFS:

class Solution {int[][] dir={{0,1},{0,-1},{1,0},{-1,0}};public void DFS(char[][] board,int x,int y){if(board[x][y]=='X'||board[x][y]=='A')return;//把边界O以及边界相邻的O标记为Aboard[x][y]='A';for(int i=0;i<4;i++){int nx=x+dir[i][0];int ny=y+dir[i][1];if(nx<0||ny<0||nx>=board.length||ny>=board[0].length)continue;DFS(board,nx,ny);}}public void solve(char[][] board) {//对边界进行DFSfor(int i=0;i<board.length;i++){if(board[i][0]=='O')DFS(board,i,0);if(board[i][board[0].length-1]=='O')DFS(board,i,board[0].length-1);}for(int j=1;j<board[0].length-1;j++){if(board[0][j]=='O')DFS(board,0,j);if(board[board.length-1][j]=='O')DFS(board,board.length-1,j);}for(int i=0;i<board.length;i++){for(int j=0;j<board[0].length;j++){if(board[i][j]=='O')board[i][j]='X';if(board[i][j]=='A')   //注意这两个的if顺序不能颠倒board[i][j]='O';}}}
}

BFS:

class Solution {int[][] dir={{0,1},{0,-1},{1,0},{-1,0}};public void BFS(char[][] board,int x,int y){Queue<int[]> queue =new LinkedList<>();queue.offer(new int[]{x,y});//先把O转换成Aboard[x][y]='A';while(!queue.isEmpty()){int[] cur=queue.poll();int cx=cur[0];int cy=cur[1];for(int i=0;i<4;i++){int nx=cx+dir[i][0];int ny=cy+dir[i][1];if(nx<0||ny<0||nx>=board.length||ny>=board[0].length)continue;if(board[nx][ny]=='X' ||board[nx][ny]=='A')continue;queue.offer(new int[]{nx,ny});board[nx][ny]='A';   //把O入队并修改成A}}}public void solve(char[][] board) {//对边界进行BFSfor(int i=0;i<board.length;i++){if(board[i][0]=='O')BFS(board,i,0);if(board[i][board[0].length-1]=='O')BFS(board,i,board[0].length-1);}for(int j=1;j<board[0].length-1;j++){if(board[0][j]=='O')BFS(board,0,j);if(board[board.length-1][j]=='O')BFS(board,board.length-1,j);}for(int i=0;i<board.length;i++){for(int j=0;j<board[0].length;j++){if(board[i][j]=='O')board[i][j]='X';if(board[i][j]=='A')   //注意这两个的if顺序不能颠倒board[i][j]='O';}}}
}

417、太平洋大西洋水流问题

有一个 m × n 的矩形岛屿,与 太平洋大西洋 相邻。 “太平洋” 处于大陆的左边界和上边界,而 “大西洋” 处于大陆的右边界和下边界。

这个岛被分割成一个由若干方形单元格组成的网格。给定一个 m x n 的整数矩阵 heights , heights[r][c] 表示坐标 (r, c) 上单元格 高于海平面的高度

岛上雨水较多,如果相邻单元格的高度 小于或等于 当前单元格的高度,雨水可以直接向北、南、东、西流向相邻单元格。水可以从海洋附近的任何单元格流入海洋。

返回网格坐标 result 的 2D 列表 ,其中 result[i] = [ri, ci] 表示雨水从单元格 (ri, ci) 流动 既可流向太平洋也可流向大西洋

示例 1:

输入: heights = [[1,2,2,3,5],[3,2,3,4,4],[2,4,5,3,1],[6,7,1,4,5],[5,1,1,2,4]]
输出: [[0,4],[1,3],[1,4],[2,2],[3,0],[3,1],[4,0]]

思路:与之前类似,本题只需要设置两个数组来分别记录能流入太平洋和流入大西洋的位置,然后就可以找到既可流向太平洋也可流向大西洋的位置,因为海洋是在周边,所以还是要从周边进行遍历。标记能流入到周边的

DFS:

class Solution {int[][] dir ={{0,1},{0,-1},{1,0},{-1,0}};//使用DFS来找到能到达海洋的位置public void DFS(int[][] heights,boolean[][] mark,int x,int y){if(mark[x][y]==true)return;mark[x][y]=true;for(int i=0;i<4;i++){int nx=x+dir[i][0];int ny=y+dir[i][1];if(nx<0||ny<0||nx>=heights.length||ny>=heights[0].length)continue;if(heights[x][y]>heights[nx][ny])  //能流入到周边的continue;DFS(heights,mark,nx,ny);	}}public List<List<Integer>> pacificAtlantic(int[][] heights) {List<List<Integer>> ans =new ArrayList<>();int m=heights.length;int n=heights[0].length;boolean[][] pacific=new boolean[m][n];    //标记能流入太平洋的位置boolean[][] atlantic=new boolean[m][n];   //标记能流入大西洋的位置for(int i=0;i<m;i++){DFS(heights,pacific,i,0); //遍历最左列DFS(heights,atlantic,i,n-1);  //遍历最右列}for(int j=0;j<n;j++)//这个循环要从0开始,因为和大西洋也是相邻的{DFS(heights,pacific,0,j);   //最上列DFS(heights,atlantic,m-1,j); //最下列}for(int i=0;i<m;i++){for(int j=0;j<n;j++){if(pacific[i][j]==true && atlantic[i][j]==true)ans.add(List.of(i,j));}}return ans;}
}

BFS:

class Solution {int[][] dir ={{0,1},{0,-1},{1,0},{-1,0}};//使用BFS来找到能到达海洋的位置public void BFS(int[][] heights,boolean[][] mark,int x,int y){Queue<int[]> queue =new LinkedList<>();queue.offer(new int[]{x,y});mark[x][y]=true;while(!queue.isEmpty()){int[] cur=queue.poll();int cx=cur[0];int cy=cur[1];for(int i=0;i<4;i++){int nx=cx+dir[i][0];int ny=cy+dir[i][1];if(nx<0||ny<0||nx>=heights.length||ny>=heights[0].length)continue;//高度不合适或者被访问过了//一定要加mark[nx][ny]==true,否则会死循环if(heights[cx][cy]>heights[nx][ny] ||mark[nx][ny]==true)continue;queue.offer(new int[]{nx,ny});  //能够到达海洋mark[nx][ny]=true;}}}public List<List<Integer>> pacificAtlantic(int[][] heights) {List<List<Integer>> ans =new ArrayList<>();int m=heights.length;int n=heights[0].length;boolean[][] pacific=new boolean[m][n];    //标记能流入太平洋的位置boolean[][] atlantic=new boolean[m][n];   //标记能流入大西洋的位置for(int i=0;i<m;i++){//可以共用一个队列,因为执行完一个BFS后,队列就空了BFS(heights,pacific,i,0); //遍历最左列BFS(heights,atlantic,i,n-1);  //遍历最右列}for(int j=0;j<n;j++)//这个循环要从0开始,因为和大西洋也是相邻的{BFS(heights,pacific,0,j);   //最上列BFS(heights,atlantic,m-1,j); //最下列}for(int i=0;i<m;i++){for(int j=0;j<n;j++){if(pacific[i][j]==true && atlantic[i][j]==true)ans.add(List.of(i,j));}}return ans;}
}

827、最大人工岛

给你一个大小为 n x n 二进制矩阵 grid 。最多 只能将一格 0 变成 1 。

返回执行此操作后,grid 中最大的岛屿面积是多少?

岛屿 由一组上、下、左、右四个方向相连的 1 形成。

示例 1:

输入: grid = [[1, 0], [0, 1]]
输出: 3
解释: 将一格0变成1,最终连通两个小岛得到面积为 3 的岛屿。

示例 2:

输入: grid = [[1, 1], [1, 0]]
输出: 4
解释: 将一格0变成1,岛屿的面积扩大为 4。

示例 3:

输入: grid = [[1, 1], [1, 1]]
输出: 4
解释: 没有0可以让我们变成1,面积依然为 4。

暴力思路:

遍历地图尝试 将每一个 0 改成1,然后去使用DFS或者BFS搜索地图中的最大的岛屿面积,整体时间复杂度为:n^4。

优化思路:

其实每次深搜遍历计算最大岛屿面积,我们都做了很多重复的工作。

只要用一次深搜把每个岛屿的面积记录下来就好。

第一步:一次遍历地图,得出各个岛屿的面积,并做编号记录。可以使用map记录,key为岛屿编号,value为岛屿面积

第二步:在遍历地图,遍历0的方格(因为要将0变成1),并统计该1(由0变成的1)周边岛屿面积,将其相邻面积相加在一起,遍历所有 0 之后,就可以得出 选一个0变成1 之后的最大面积。

拿如下地图的岛屿情况来举例: (1为陆地)

第一步,则遍历题目,并将岛屿到编号和面积上的统计,过程如图所示:

本过程代码如下:

class Solution {int[][] dir={{0,1},{0,-1},{1,0},{-1,0}};int count=0;boolean[][] visited;  //访问数组//使用DFS把相邻的陆地都标记成一样的数字public void DFS(int[][] grid,int x,int y,int mark){if(visited[x][y]||grid[x][y]==0)return;visited[x][y]=true; //标记访问过grid[x][y]=mark;   //给陆地添加新标记count++;for(int i=0;i<4;i++){int nx=x+dir[i][0];int ny=y+dir[i][1];if(nx<0||ny<0||nx>=grid.length||ny>=grid[0].length)continue;DFS(grid,nx,ny,mark);}}public int largestIsland(int[][] grid) {int n=grid.length;int m=grid[0].length;visited=new boolean[n][m];int[] gridNum=new int[n*m]; //记录每一个岛屿的面积int mark=2;boolean isAllGrid = true; // 标记是否整个地图都是陆地for(int i=0;i<n;i++){for(int j=0;j<m;j++){if(grid[i][j]==0) isAllGrid=false;if(grid[i][j]==1 &&visited[i][j]==false){count=0;   //每次DFS之前把count清空DFS(grid,i,j,mark);  //使用DFS把相邻的陆地都标记成一样的数字gridNum[mark]=count;mark++;}}}}
}

这个过程的时间复杂度是n²,因为n * n这个方格地图中,每个节点我们就遍历一次,并不会重复遍历

第二步过程如图所示

也就是遍历每一个0的方格,并统计其相邻岛屿面积,最后取一个最大值。这个过程的时间复杂度也为 n²。

所以整个代码的复杂度就是n²

class Solution {int[][] dir={{0,1},{0,-1},{1,0},{-1,0}};int count=0;boolean[][] visited;  //访问数组//使用DFS把相邻的陆地都标记成一样的数字public void DFS(int[][] grid,int x,int y,int mark){if(visited[x][y]||grid[x][y]==0)return;visited[x][y]=true; //标记访问过grid[x][y]=mark;   //给陆地添加新标记count++;for(int i=0;i<4;i++){int nx=x+dir[i][0];int ny=y+dir[i][1];if(nx<0||ny<0||nx>=grid.length||ny>=grid[0].length)continue;DFS(grid,nx,ny,mark);}}public int largestIsland(int[][] grid) {int n=grid.length;int m=grid[0].length;visited=new boolean[n][m];int[] gridNum=new int[n*m+2]; //记录每一个岛屿的面积,加2是防止矩阵只有1的长度int mark=2;boolean isAllGrid = true; // 标记是否整个地图都是陆地//把相连的岛屿进行标记for(int i=0;i<n;i++){for(int j=0;j<m;j++){if(grid[i][j]==0) isAllGrid=false;if(grid[i][j]==1 &&visited[i][j]==false){count=0;   //每次DFS之前把count清空DFS(grid,i,j,mark);  //使用DFS把相邻的陆地都标记成一样的数字gridNum[mark]=count;mark++;}}}//把一个0变为陆地,并计算最大的面积if(isAllGrid)return n*m;int result=0;for(int i=0;i<n;i++){for(int j=0;j<m;j++){if(grid[i][j]==0)   //把0换成1{int num=1;   //0本身的面积boolean[] visitedGrid =new boolean[mark]; //每次变过之后,标记0周边访问过的岛屿,有可能相邻的是属于同一个岛屿for(int k=0;k<4;k++){int nx=i+dir[k][0];int ny=j+dir[k][1];if(nx<0||ny<0||nx>=grid.length||ny>=grid[0].length)continue;if(visitedGrid[grid[nx][ny]]==true)continue;num+=gridNum[grid[nx][ny]];  // 把相邻四面的岛屿数量加起来visitedGrid[grid[nx][ny]]=true;  // 标记该岛屿已经添加过}result=Math.max(result,num);}}}return result;}
}

把问题转换为图的遍历

127、单词接龙

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

  • 每一对相邻的单词只差一个字母。
  • 对于 1 <= i <= k 时,每个 si 都在 wordList 中。注意, beginWord 不需要在 wordList 中。
  • sk == endWord

给你两个单词 beginWord 和 endWord 和一个字典 wordList ,返回 beginWordendWord最短转换序列 中的 __单词数目 。如果不存在这样的转换序列,返回 0 。

示例 1:

输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
输出:5
解释:一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog", 返回它的长度 5。

示例 2:

输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"]
输出:0
解释:endWord "cog" 不在字典中,所以无法进行转换。

这道题要解决两个问题:

  • 图中的线是如何连在一起的
  • 起点和终点的最短路径长度

首先题目中并没有给出点与点之间的连线,而是要我们自己去连,条件是字符只能差一个,所以判断点与点之间的关系,要自己判断是不是差一个字符,如果差一个字符,那就是有链接

然后就是求起点和终点的最短路径长度,这里无向图求最短路,广搜最为合适,广搜只要搜到了终点,那么一定是最短的路径。因为广搜就是以起点中心向四周扩散的搜索。

另外需要有一个注意点:

  • 本题是一个无向图,需要用标记位,标记着节点是否走过,否则就会死循环!
  • 本题给出集合是数组型的,可以转成set结构,查找更快一些

转换为图求最短路径

使用一个队列记录bfs的结点,并使用一个map记录访问过的结点和路径长度,使用Hashset判断更换后的单词是否在列表中。

class Solution {public int ladderLength(String beginWord, String endWord, List<String> wordList) {HashSet<String> wordset =new HashSet<>(wordList); //加快判断速度if(wordset.size()==0 ||!wordset.contains(endWord))return 0;Queue<String> queue =new LinkedList<>();queue.offer(beginWord);//记录单词对应的路径长度,同时也有标记功能,标记wordList的访问过的单词Map<String,Integer> map =new HashMap<>(); map.put(beginWord,1);while(!queue.isEmpty()){String word=queue.poll(); //从队列中取出第一个单词int path=map.get(word);   //单词对应的路径长度for(int i=0;i<word.length();i++)   //遍历单词每个字符,把单词进行替换{char[] chars=word.toCharArray(); //变为数组,方便操作for(char k='a';k<='z';k++){chars[i]=k;String newword=String.valueOf(chars); //替换成新单词if(endWord.equals(newword))return path+1;//如果新单词在列表中,并且没有访问过if(wordset.contains(newword) && !map.containsKey(newword)){queue.offer(newword); //加入队列map.put(newword,path+1); //记录单词对应的路径长度}}}} return 0;//未找到}
}

841、钥匙和房间

有 n 个房间,房间按从 0 到 n - 1 编号。最初,除 0 号房间外的其余所有房间都被锁住。你的目标是进入所有的房间。然而,你不能在没有获得钥匙的时候进入锁住的房间。

当你进入一个房间,你可能会在里面找到一套不同的钥匙,每把钥匙上都有对应的房间号,即表示钥匙可以打开的房间。你可以拿上所有钥匙去解锁其他房间。

给你一个数组 rooms 其中 rooms[i] 是你进入 i 号房间可以获得的钥匙集合。如果能进入 所有 房间返回 true,否则返回 false。

示例 1:

输入:rooms = [[1],[2],[3],[]]
输出:true
解释:
我们从 0 号房间开始,拿到钥匙 1。
之后我们去 1 号房间,拿到钥匙 2。
然后我们去 2 号房间,拿到钥匙 3。
最后我们去了 3 号房间。
由于我们能够进入每个房间,我们返回 true。

示例 2:

输入:rooms = [[1,3],[3,0,1],[2],[0]]
输出:false
解释:我们不能进入 2 号房间。

相当于一个有向图,rooms[i] 就是i结点能到达的结点。能到达就标记,然后看最后能否标记完

DFS

class Solution {boolean[] visited;//DFS的第一种写法,处理当前访问的结点public void DFS(List<List<Integer>> rooms,int curKey){if(visited[curKey])return;visited[curKey]=true;for(int key:rooms.get(curKey)){DFS(rooms,key);}}//DFS的第二种写法,处理下一个结点public void DFS(List<List<Integer>> rooms,int curKey){//这里 没有终止条件,而是在 处理下一层节点的时候来判断for(int key:rooms.get(curKey)){if (visited[key] == false) { // 处理下一层节点,判断是否要进行递归{   visited[key] = true;DFS(rooms,key);}}}public boolean canVisitAllRooms(List<List<Integer>> rooms) {visited=new boolean[rooms.size()]; //标记数组DFS(rooms,0);for(int i=0;i<visited.length;i++){if(visited[i]==false)return false;}return true;}}

什么本题就没有回溯呢?

代码中可以看到dfs函数下面并没有回溯的操作。

此时就要在思考本题的要求了,本题是需要判断 0节点是否能到所有节点,那么我们就没有必要回溯去撤销操作了,只要遍历过的节点一律都标记上。

那什么时候需要回溯操作呢?

当我们需要搜索一条可行路径的时候,就需要回溯操作了,因为没有回溯,就没法“调头”

BFS

class Solution {//因为只需要遍历一次BFS 所以可以写在主函数里public boolean canVisitAllRooms(List<List<Integer>> rooms) {boolean[] visited=new boolean[rooms.size()]; //标记数组Queue<Integer> queue =new LinkedList<>();queue.add(0);visited[0]=true;while(!queue.isEmpty()){int curKey=queue.poll();for(int key:rooms.get(curKey)){if(visited[key]==false){queue.add(key);visited[key]=true;}}}for(int i=0;i<visited.length;i++){if(visited[i]==false)return false;}return true;}
}

463、岛屿的周长

给定一个 row x col 的二维网格地图 grid ,其中:grid[i][j] = 1 表示陆地, grid[i][j] = 0 表示水域。

网格中的格子 水平和垂直 方向相连(对角线方向不相连)。整个网格被水完全包围,但其中恰好有一个岛屿(或者说,一个或多个表示陆地的格子相连组成的岛屿)。

岛屿中没有“湖”(“湖” 指水域在岛屿内部且不和岛屿周围的水相连)。格子是边长为 1 的正方形。网格为长方形,且宽度和高度均不超过 100 。计算这个岛屿的周长。

示例 1:

输入:grid = [[0,1,0,0],[1,1,1,0],[0,1,0,0],[1,1,0,0]]
输出:16
解释:它的周长是上面图片中的 16 个黄色的边

思路:

岛屿问题最容易让人想到BFS或者DFS,但是这道题还真的没有必要,别把简单问题搞复杂了

遍历每一个空格,遇到岛屿,计算其上下左右的情况,遇到水域或者出界的情况,就可以计算边了

class Solution {public int islandPerimeter(int[][] grid) {int[][] dir={{0,1},{0,-1},{1,0},{-1,0}};int count=0;for(int i=0;i<grid.length;i++){for(int j=0;j<grid[0].length;j++){if(grid[i][j]==1)  //遇到陆地{for(int k=0;k<4;k++){int nx=i+dir[k][0];int ny=j+dir[k][1];//新位置遇到边界,不要忘记continue,因为下面会利用到这些坐标if(nx<0||ny<0||nx>=grid.length||ny>=grid[0].length){count++;continue;}if(grid[nx][ny]==0)  //x新位置遇到水域,周长加1count++;}}}}return count;}
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Windows系统启动MongoDB报错无法连接服务器

文章目录 发现问题解决办法 发现问题 1&#xff09;、先是发现执行 mongo 命令&#xff0c;启动报错&#xff1a; error: MongoNetworkError: connect ECONNREFUSED 127.0.0.1:27017&#xff1b; 2&#xff09;、再检查 MongoDB 进程 tasklist | findstr mongo 发现没有进程&a…

爬虫基础--requests模块

1、requests模块的认识 requests模块的认识请跳转到 requests请求库使用_使用requests库-CSDN博客 2、爬取数据 这里我们以b站动漫追番人数为例。 首先进去b站官网 鼠标右键点击检查或者键盘的F12&#xff0c;进入开发者模式。&#xff08;这里我使用的是谷歌浏览器为例&#…

【JVM】—深入理解G1回收器—回收过程详解

深入理解G1回收器—回收过程详解 ⭐⭐⭐⭐⭐⭐ Github主页&#x1f449;https://github.com/A-BigTree 笔记链接&#x1f449;https://github.com/A-BigTree/Code_Learning ⭐⭐⭐⭐⭐⭐ 如果可以&#xff0c;麻烦各位看官顺手点个star~&#x1f60a; 文章目录 深入理解G1回收…

基于PERL语言的MS中CASTEP模块批量提交计算脚本

在现代科学研究中&#xff0c;高效的计算工具对于推动科研进步具有不可估量的价值。为了满足广大科研工作者在材料科学、化学、物理等领域日益增长的计算需求&#xff0c;我们特别推出了一款基于Perl语言的MS CASTEP模块批量提交计算脚本。 一、批量提交&#xff0c;高效处理 …

Vulnhub打靶-Empire-LupinOne

基本信息 靶机下载&#xff1a;https://download.vulnhub.com/empire/01-Empire-Lupin-One.zip 攻击机器&#xff1a;192.168.20.128&#xff08;Windows操作系统&#xff09;& 192.168.20.138&#xff08;kali&#xff09; 提示信息&#xff1a; 这个盒子被创建为中等…

RNN,LSTM,GRU的区别和联系? RNN的梯度消失问题?如何解决?

RNN&#xff0c;LSTM&#xff0c;GRU的区别和联系? RNN&#xff08;Recurrent Neural Network&#xff09;、LSTM&#xff08;Long Short-Term Memory&#xff09;和GRU&#xff08;Gated Recurrent Unit&#xff09;都是用于处理序列数据的神经网络模型&#xff0c;它们之间…

《黑神话悟空》各章节boss顺序汇总

第一章BOSS顺序&#xff1a; 1、牯护院&#xff1a;犀牛精&#xff0c;位于苍狼岭娟&#xff0c;击败后能获得定身术。 2、广智&#xff1a;火刀狼&#xff0c; 位于观音禅院&#xff0c;击败后获得广智变身&#xff0c;记得敲钟。 3、蓝皮幽魂&#xff1a;蓝皮大头&#xff0…

间充质干细胞疗法迎来快速发展:国内新药申请超93项,全球临床试验超1300项

间充质干细胞&#xff08;Mesenchymal Stem Cells, MSCs&#xff09;独一无二的特性和机制构建了非凡的治疗前景和成药空间。截止到2024年10月18日&#xff0c;临床试验注册库clinicaltrials.gov网站上有关“Mesenchymal Stem Cell”的项目有1303项。在国家药品监督管理局药品审…

Active Directory(活动目录)密码审核工具

什么是Active Directory密码审核 Active Directory密码审核涉及监控用户密码的状态及其身份验证尝试&#xff0c;以便 IT 管理员收到有关弱 Active Directory密码或任何异常身份验证行为的通知。 Active Directory密码审核可帮助管理员评估用户密码的强度并采取必要措施来加强…

Composio:AI 开发利器,集成 100+ 工具,简化智能体构建

❤️ 如果你也关注大模型与 AI 的发展现状&#xff0c;且对大模型应用开发非常感兴趣&#xff0c;我会快速跟你分享最新的感兴趣的 AI 应用和热点信息&#xff0c;也会不定期分享自己的想法和开源实例&#xff0c;欢迎关注我哦&#xff01; 微信公众号&#xff5c;搜一搜&…

SimpleLive 1.7.3 | TV+手机,聚合抖B虎鱼四大直播

SimpleLive是一款聚合多个直播平台的应用程序&#xff0c;内置虎牙、斗鱼、哔哩哔哩及抖音直播&#xff0c;提供无广告体验&#xff0c;支持弹幕显示调整、夜间模式切换等功能&#xff0c;无需登录即可关注不同平台主播并查看其直播状态。下载安装APK后打开应用&#xff0c;选择…

Web Service

目录 1、概览2、SOA架构2.1 Web Service的基础协议2.2 Web Service协议栈 3 Web Service的分类3.1 应用领域3.2 服务器类型 4 厂商支持4.1 Java EE4.2 .NET4.3 WebSphere 5 其他5.1 微服务与 Web Service5.1.1 微服务与 Web 服务之间的区别5.1.2 微服务、 Web 服务的最佳实践 5…

laravel 查询数据库

数据库准备 插入 三行 不同的数据 自行搭建 laravel 工程 参考 工程创建点击此处 laravel 配置 数据库信息 DB_CONNECTIONmysql #连接什么数据库 DB_HOST127.0.0.1 # 连接 哪个电脑的 ip &#xff08;决定 电脑 本机&#xff09; DB_PORT3306 # 端口 DB_DATABASEyanyu…

FloodFill 算法(DFS)

文章目录 FloodFill 算法&#xff08;DFS&#xff09;图像渲染岛屿数量岛屿的最大面积被围绕的区域太平洋大西洋水流问题扫雷游戏衣橱整理 FloodFill 算法&#xff08;DFS&#xff09; 漫水填充(Flood Fi)算法是一种图像处理算法&#xff0c;在计算机图形学和计算机视觉中被广泛…

超详细的 Stable Diffusion Webui入门教程(二)基础操作

前言 工欲善其事&#xff0c;必先利其器&#xff01;今天我们聊聊 Stable Diffusion WebUI 的基础操作以及各个参数都代表了什么。 所有的AI设计工具&#xff0c;安装包、模型和插件&#xff0c;都已经整理好了&#xff0c;&#x1f447;获取~ 一、大模型的切换 在 Stable D…

【从零开始的LeetCode-算法】3185. 构成整天的下标对数目 II

给你一个整数数组 hours&#xff0c;表示以 小时 为单位的时间&#xff0c;返回一个整数&#xff0c;表示满足 i < j 且 hours[i] hours[j] 构成 整天 的下标对 i, j 的数目。 整天 定义为时间持续时间是 24 小时的 整数倍 。 例如&#xff0c;1 天是 24 小时&#xff0c…

[Vue3核心语法] ref、reactive响应式数据

定义: ref用来定义&#xff1a;基本类型数据、对象类型数据&#xff1b; reactive用来定义&#xff1a;对象类型数据。 使用原则: 若需要一个基本类型的响应式数据&#xff0c;必须使用ref。 若需要一个响应式对象&#xff0c;层级不深&#xff0c;ref、reactive都可以。 …

项目管理这些问题,你是不是忍了很久?

项目管理中常见的问题&#xff0c;你是不是早就感到无奈了&#xff1f;项目进度滞后、成本超支、团队协作不畅、任务分配模糊、资源分配不合理……这些问题常常让人感到力不从心。 无论是关键节点的拖延&#xff0c;还是多部门间的沟通障碍&#xff0c;最终都会拖慢项目进展&a…

京东大模型革命电商搜推技术:挑战、实践与未来趋势

大模型对搜推技术产生了深远的影响&#xff0c;极大地推动了搜推技术的演进趋势&#xff0c;使得搜推更加的智能化和个性化&#xff0c;然而在搜推中引入大模型时同样面临一系列的挑战&#xff0c;例如商品知识的幻觉&#xff0c;复杂查询的理解&#xff0c;个性化商品推荐&…

酒店预订订房小程序源码系统 多酒店入驻+打造类似美团的酒店模式 带完整的安装代码包以及搭建部署教程

系统概述 随着移动互联网的普及&#xff0c;小程序因其轻量级、无需下载安装、即用即走的特点&#xff0c;迅速成为各行业的标配。对于酒店预订行业而言&#xff0c;小程序不仅能够有效提升用户体验&#xff0c;还能降低运营成本&#xff0c;提高转化率。本源码系统正是基于这…