来自灵神网格图题单。
1. 网格图
1.1. LC 200 岛屿数量
这题我一开始想繁了,想维护并查集,然后看等价类个数。其实完全没有必要。因为连通分量深搜到头就可以直接给答案计数+1。利用vis数组维护访问过的点,然后碰到新连通分量重新深搜即可。因为有vis数组,所以每个节点至多访问一次,即O(nm)的复杂度。
import java.util.Arrays;class Solution {boolean[] vis;int m,n;int[][] direction = new int[][]{{-1,0},{0,1},{1,0},{0,-1}};public int numIslands(char[][] grid) {m = grid.length;n = grid[0].length;vis = new boolean[m*n];int ans = 0;for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {if(grid[i][j]=='1'&&!vis[i*n+j]){dfs(grid,i,j);ans++;}}}return ans;}private void dfs(char[][] grid,int x,int y){if(vis[x*n+y]){return;}vis[x*n+y] = true;int nx,ny;for (int[] dir : direction) {nx = x+dir[0];ny = y+dir[1];if(indexValid(nx,ny)&&grid[nx][ny]=='1'){dfs(grid,nx,ny);}}}private boolean indexValid(int x,int y){return x>=0 && x<m && y>=0 && y<n;}
}
1.2. LC 695 岛屿的最大面积
这题和LC 200其实一样的。前者统计连通分量个数,这个统计最大连通分量元素个数。还是维护vis防止走重复,遇到新连通分支就统计这个连通分支元素数量,维护最大值就行。
class Solution {boolean[] vis;int m,n;int[][] directions = new int[][]{{-1,0},{0,1},{1,0},{0,-1}};public int maxAreaOfIsland(int[][] grid) {m = grid.length;n = grid[0].length;vis = new boolean[n*m];int max = 0;for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {if(!vis[i*n+j] && grid[i][j]==1){max = Math.max(max,dfs(grid,i,j));}}}return max;}private int dfs(int[][] g,int x,int y){if(vis[x*n+y]){return 0;}vis[x*n+y] = true;int nx,ny;int cnt = 0;for (int[] direction : directions) {nx = x+direction[0];ny = y+direction[1];if(indexValid(nx,ny) && g[nx][ny]==1){cnt += dfs(g,nx,ny);}}return cnt+1;}private boolean indexValid(int x,int y){return x>=0 && x<m && y>=0 && y<n;}
}
1.3. LC 面试题16.19. 水域大小
和LC695差不多。维护每个连通分量大小,排个序返回就行。
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;class Solution {boolean[] vis;int m,n;int[][] dirs = new int[][]{{-1,0},{0,1},{1,0},{0,-1},{-1,-1},{-1,1},{1,-1},{1,1}};public int[] pondSizes(int[][] land) {m = land.length;n = land[0].length;vis = new boolean[m*n];List<Integer> tmp = new ArrayList<>();for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {if(!vis[i*n+j]&&land[i][j]==0){tmp.add(dfs(land,i,j));}}}int[] ans = new int[tmp.size()];for (int i = 0; i < tmp.size(); i++) {ans[i] = tmp.get(i);}Arrays.sort(ans);return ans;}private int dfs(int[][] l,int x,int y){if(vis[x*n+y]){return 0;}vis[x*n+y]=true;int cnt = 0;int nx,ny;for (int[] dir : dirs) {nx = x+dir[0];ny = y+dir[1];if(indexValid(nx,ny)&&l[nx][ny]==0){cnt += dfs(l,nx,ny);}}return cnt+1;}private boolean indexValid(int x,int y){return x>=0 && x<m && y>=0 && y<n;}
}
1.4. LC 463 岛屿的周长
和200/695/16.19差不多,就是查一个连通分量。记得重复边不仅没有增量反而-1。另外由于题目明确说了就一个连通分量,因此查到一个后可以直接结束了。
class Solution {boolean[] vis;int m,n;int[][] dirs = new int[][]{{-1,0},{0,1},{1,0},{0,-1}};public int islandPerimeter(int[][] grid) {m = grid.length;n = grid[0].length;vis = new boolean[m*n];int ans = 0;for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {if(!vis[i*n+j]&&grid[i][j]==1){ans = dfs(grid,i,j);break;}}}return ans;}private int dfs(int[][] g,int x,int y){if(vis[x*n+y]){return 0;}vis[x*n+y]=true;int cnt = 4;int nx,ny;for (int[] dir : dirs) {nx = x+dir[0];ny = y+dir[1];if(indexValid(nx,ny)&&g[nx][ny]==1){cnt+=dfs(g,nx,ny)-1;}}return cnt;}private boolean indexValid(int x,int y){return x>=0 && x<m && y>=0 && y<n;}
}
1.5. LC 2658 网格图中鱼的最大数目
这题一开始读错题了,以为是最大化连通分量中的最大值。但实际上是最大化连通分量元素和。还是跟以前一样套路,分开深搜维护最大值即可。
class Solution {boolean[] vis;int m,n;int[][] dirs = new int[][]{{-1,0},{0,1},{1,0},{0,-1}};public int findMaxFish(int[][] grid) {m = grid.length;n = grid[0].length;vis = new boolean[m*n];int max = 0;for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {if(!vis[i*n+j]&&grid[i][j]>0){max = Math.max(max,dfs(grid,i,j));}}}return max;}private int dfs(int[][] g,int x,int y){if(vis[x*n+y]){return 0;}vis[x*n+y]=true;int nx,ny;int cnt = g[x][y];for (int[] dir : dirs) {nx = x+dir[0];ny = y+dir[1];if(indexValid(nx,ny)&&g[nx][ny]>0){cnt += dfs(g,nx,ny);}}return cnt;}private boolean indexValid(int x,int y){return x>=0 && x<m && y>=0 && y<n;}
}
1.7. LC 1020 飞地的数量
这道题虽然A了但是思路比较差。我是直接深搜,然后打一个标记位置,深搜到边界给标记位置置true,然后深搜返回的是连通块的大小。如果标记位为false说明连通块没有到边界,这样就可以累加。
但是更好的思路是,直接从边界深搜,把能抵达的1全部标记出来,剩下没标记的都是要求的。
import java.util.Arrays;class Solution {boolean[][] vis;int[][] directions = new int[][]{{-1,0},{0,1},{1,0},{0,-1}};int m,n;boolean flag;public int numEnclaves(int[][] grid) {m = grid.length;n = grid[0].length;vis = new boolean[m][n];int sum = 0;for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {if(grid[i][j]==1&&!vis[i][j]){flag = false;int tmp = dfs(grid, i, j);if(!flag){sum+=tmp;}}}}return sum;}private int dfs(int[][] grid,int x,int y){if(vis[x][y]||grid[x][y]==0){return 0;}vis[x][y] = true;if(x==0||x==m-1||y==0||y==n-1){flag = true;}int nx,ny,sum;sum = 0;for (int[] direction : directions) {nx = x+direction[0];ny = y+direction[1];if(isValid(nx,ny)){sum += dfs(grid,nx,ny);}}return sum+1;}private boolean isValid(int x,int y){return x>=0 && x<m && y>=0 &&y<n;}
}
这个是我的。
class Solution {public int numEnclaves(int[][] grid) {int m = grid.length;int n = grid[0].length;for(int i = 0; i < m; i ++){if(grid[i][0] == 1) dfs(grid, i, 0, m, n);if(grid[i][n - 1] == 1) dfs(grid, i, n - 1, m , n);}for(int i = 0; i < n; i ++){if(grid[0][i] == 1) dfs(grid, 0, i, m, n);if(grid[m - 1][i] == 1) dfs(grid, m - 1, i, m ,n);}int res = 0;for(int i = 0; i < m; i ++){for(int j = 0; j < n; j ++){if(grid[i][j] == 1){res ++;}}}return res;}public void dfs(int[][] grid, int i, int j, int m, int n){if(i < 0 || j < 0 || i >= m || j >= n) return;if(grid[i][j] == 0) return;grid[i][j] = 0;dfs(grid, i + 1, j, m, n);dfs(grid, i - 1, j, m ,n);dfs(grid, i, j + 1, m, n);dfs(grid, i, j - 1, m, n);}
}
更好的思路。
1.8. LC 1254 统计封闭岛屿的数目
可以这么想,如果一个0的连通块存在元素在边界上,那么就不是封闭岛屿,反过来就是。这样深搜的时候检查是否到达过边界。如果没有到达过的话增1就可以了。
import java.util.Arrays;class Solution {int m,n;boolean[] vis;int[][] directions = new int[][]{{-1,0},{0,1},{1,0},{0,-1}};public int closedIsland(int[][] grid) {m = grid.length;n = grid[0].length;vis = new boolean[m*n];Arrays.fill(vis,false);int ans = 0;for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {if(!vis[i*n+j]&&grid[i][j]!=1){if(!dfs(grid,i,j)){ans++;}}}}return ans;}private boolean isOnEdge(int x,int y){return x==0||y==0||x==m-1||y==n-1;}private boolean isValid(int x,int y){return x>=0 && x<m && y>=0 && y<n;}private boolean dfs(int[][] grid,int x,int y){if(grid[x][y]==1||vis[x*n+y]){return false;}vis[x*n+y] = true;int nx,ny;boolean flag = isOnEdge(x,y);for (int[] direction : directions) {nx = x+direction[0];ny = y+direction[1];if(isValid(nx,ny)){flag|=dfs(grid,nx,ny);}}return flag;}
}
1.9. LC 130 被围绕的区域
这题别和上题一样判断是否连通块有边界元素,因为判完再染之前深搜的可能漏染了。
可以这样,先从边界深搜,把边界上的O所在的连通块全访问掉。这样剩下的就是内部的了(被围绕的O),然后深搜内部染色就行。
class Solution {int m,n;boolean[][] vis;int[][] directions = new int[][]{{-1,0},{0,1},{1,0},{0,-1}};public void solve(char[][] board) {m = board.length;n = board[0].length;vis = new boolean[m][n];for (int i = 0; i < n; i++) {if(board[0][i]=='O'&&!vis[0][i]){dfs(board,0,i,true);}}for (int i = 0; i < n; i++) {if(board[m-1][i]=='O'&&!vis[m-1][i]){dfs(board,m-1,i,true);}}for (int i = 0; i < m; i++) {if(board[i][0]=='O'&&!vis[i][0]){dfs(board,i,0,true);}}for (int i = 0; i < m; i++) {if(board[i][n-1]=='O'&&!vis[i][n-1]){dfs(board,i,n-1,true);}}for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {if(board[i][j]=='O'&&!vis[i][j]){dfs(board,i,j,false);}}}}private void dfs(char[][] g,int x,int y,boolean OnEdge){if(g[x][y]=='X'||vis[x][y]){return;}vis[x][y] = true;int nx,ny;for (int[] direction : directions) {nx = x+direction[0];ny = y+direction[1];if(isValid(nx,ny)){dfs(g,nx,ny,OnEdge);}}if(!OnEdge){g[x][y] ='X';}}private boolean isValid(int x,int y){return x>=0 && x<m && y>=0 && y<n;}
}
1.10. LC 1391 检查网格中是否存在有效路径
这题很恶心。首先要选择不同形状街道的行走方向,比如第一个街道既可以从左往右,又可以从右到左;第二得保证下一个格子的街道形状能够对接的上这个各自的街道形状。比如1可以拼接5但不能拼接6。这两个条件我打表了两个巨繁的数组。其中directions[i]是一个二维数组,表示值为i+1的单元格的行走方向,每个行走方向都是一个二维向量。accept[i]也是一个二维数组,表示值为i+1的单元格可以接受的街道形状的对应id-1。
class Solution {int m,n;int[][][] directions = new int[][][]{{{0,-1},{0,1}},{{-1,0},{1,0}},{{0,-1},{1,0}},{{0,1},{1,0}},{{0,-1},{-1,0}},{{0,1},{-1,0}}};int[][][] accept = new int[][][]{{{0,3,5},{0,2,4}},{{1,2,3},{1,4,5}},{{0,3,5},{1,4,5}},{{0,2,4},{1,4,5}},{{0,3,5},{1,2,3}},{{0,2,4},{1,2,3}}};boolean[][] vis;public boolean hasValidPath(int[][] grid) {m = grid.length;n = grid[0].length;vis = new boolean[m][n];return dfs(grid,0,0);}private boolean dfs(int[][] grid,int x,int y){if(x==m-1 && y==n-1){return true;}vis[x][y] = true;int nx,ny;int way = grid[x][y]-1;boolean flag = false;for (int i = 0; i < directions[way].length; i++) {nx = x+directions[way][i][0];ny = y+directions[way][i][1];if(isValid(nx,ny)&&!vis[nx][ny]){if(contains(accept[way][i],grid[nx][ny]-1)){flag|=dfs(grid,nx,ny);}}}return flag;}private boolean isValid(int x,int y){return x>=0 && x<m && y>=0 && y<n;}private boolean contains(int[] arr,int target){for (int i : arr) {if(i==target){return true;}}return false;}
}
其实还好,O(mn)的。就是写起来要有一堆规则,很恶心。