目录
一、695. 岛屿的最大面积
1.1 题目描述
给定一个包含了一些 0 和 1 的非空二维数组 grid 。(难度中等)
一个 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在水平或者竖直方向上相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。
找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为 0 。)
1.2 代码
1.2.1 DFS
算法思路:力扣
我们想知道网格中每个连通形状的面积,然后取最大值。
如果我们在一个土地上,以 44个方向探索与之相连的每一个土地(以及与这些土地相连的土地),那么探索过的土地总数将是该连通形状的面积。
为了确保每个土地访问不超过一次,我们每次经过一块土地时,将这块土地的值置为 0。这样我们就不会多次访问同一土地。
class Solution {
private int m,n;
private int[][] direction={{1,0},{-1,0},{0,-1},{0,1}};//上下左右四个方向
public int maxAreaOfIsland(int[][] grid) {
m = grid.length;
n= grid[0].length;
if (m==0 || n==0 || grid==null){
return 0;
}
int maxArea=0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
maxArea = Math.max(maxArea,dfs(grid,i,j));
}
}
return maxArea;
}
public int dfs(int[][]grid,int row,int column){
if (row<0 ||row>=m ||column <0 ||column>=n|| grid[row][column] == 0){
return 0;
}
grid[row][column]=0;
int area=1;
for (int[] d:direction){
area+=dfs(grid,row+d[0],column+d[1]);
}
return area;
}
}
复杂度分析
时间复杂度:O(R×C)。其中 R 是给定网格中的行数,C 是列数。我们访问每个网格最多一次。
空间复杂度:O(R×C),递归的深度最大可能是整个网格的大小,因此最大可能使用O(R×C) 的栈空间。
注意不要把curCount当做变量传递,因为curCount值不能被传递,需要写为全局变量。
class Solution695 {
//注意不要把curCount当做变量传递,因为curCount值不能被传递,需要写为全局变量
static int max,row,col,curCount;
static int[][] direction={{0,1},{1,0},{0,-1},{-1,0}};
public static int maxAreaOfIsland(int[][] grid) {
row=grid.length;
col=grid[0].length;
curCount=0;
max=0;
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
if (grid[i][j]==1){
curCount=0;
backing(grid,i,j);
//max=Math.max(max,curCount);//此处curCount局部变量和全局变量时返回值都为0,函数里面才有准确的值
}
}
}
return max;
}
private static void backing(int[][] grid, int index1, int index2) {
if (index1<0 || index1>=row || index2<0 || index2>=col || grid[index1][index2]==0 ) return;
curCount++;
grid[index1][index2]=0;
for (int[] dir:direction){
backing(grid,index1+dir[0],index2+dir[1]);
}
max=Math.max(max,curCount);
}
}
二、200. 岛屿数量
2.1 题目描述
给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。(难度中等)
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。此外,你可以假设该网格的四条边均被水包围。
2.2 代码
2.2.1 DFS
做法与上题大致相同,不同的是,此题遇到‘1’时,深度遍历此‘1’所有的连通的陆地,将其都置为0。则下一次遇到‘1’时,则是新的岛屿,islanNum++。
private int[][] direction={{1,0},{-1,0},{0,-1},{0,1}};//上下左右四个方向
private int m,n;
public int numIslands(char[][] grid) {
m = grid.length;
n= grid[0].length;
if (m==0 || n==0 || grid==null){
return 0;
}
int islandNum=0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j]=='1'){
dfs(grid,i,j);
islandNum++;
}
}
}
return islandNum;
}
public void dfs(char[][] grid,int row,int column){
if (row<0 ||row>=m ||column <0 ||column>=n|| grid[row][column] == '0'){
return;
}
grid[row][column]='0';
for (int[] d :direction){
dfs(grid,row+d[0],column+d[1]);
}
}
复杂度分析:
- 时间复杂度O(MN),其中 M和 N 分别为行数和列数。
- 空间复杂度:O(MN),在最坏情况下,整个网格均为陆地,深度优先搜索的深度达到MN。
三、547. 省份数量
3.1 题目描述
有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。(难度中等)
省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。
给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。
返回矩阵中省份的数量。
3.2 代码
3.2.1 DFS
思路:力扣
可以把 n个城市和它们之间的相连关系看成图,城市是图中的节点,相连关系是图中的边,给定的矩阵isConnected 即为图的邻接矩阵,省份即为图中的连通分量。计算省份总数,等价于计算图中的连通分量数,可以通过深度优先搜索或广度优先搜索实现,也可以通过并查集实现。
- 要判断i和谁相连,则只需要看isConnected数组的第i行,若isConnected[i][m]==1,则说明i与m直接相连。因此看i与谁连通,只需要看第i行。
- 遍历城市i,将与i相连的城市都标记为访问,下一次遇到未访问的城市,则是新的省份。
int provinces;
public int findCircleNum(int[][] isConnected) {
provinces = isConnected.length;
int circle=0;
boolean[] isVisited =new boolean[provinces];
for (int i=0;i<provinces;i++){
//若i城市未访问过,则深度遍历i
if (!isVisited[i]){
circle++;
dfs(isConnected,isVisited,i);
}
}
return circle;
}
public void dfs(int[][] isConnected, boolean[] isVisited,int i){
for (int j = 0; j < provinces; j++) {
//i与j连通,且j未访问过,则深度遍历j城市,将与j相连通的城市都访问了
if (!isVisited[j] && isConnected[i][j]==1){
isVisited[j]=true;
dfs(isConnected,isVisited,j);//i与j连通,现在看j与谁连通,访问
}
}
}
四、130. 被围绕的区域
4.1 题目描述
给你一个 m x n
的矩阵 board
,由若干字符 'X'
和 'O'
,找到所有被 'X'
围绕的区域,并将这些区域里所有的 'O'
用 'X'
填充。(难度中等)
4.2 代码
4.2.1 DFS
思路:力扣
先处理边界,使用dfs,将与边界‘0’相连的‘0’都标记为‘#’,则剩余的‘O’,即为被‘X’包围的点。最后我们遍历board将为‘#’的修改为‘O’,将‘O’修改为‘X’.
public int m,n;
public int[][] direction ={{1,0},{-1,0},{0,1},{0,-1}} ;
public void solve(char[][] board) {
m = board.length;
n = board[0].length;
if (m == 0 || n == 0 || board == null)
return;
//处理边界
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
boolean isEdge = (i == 0 || i == m - 1 || j == 0 || j == n - 1);
if (isEdge && board[i][j] == 'O') {
dfs(board, i, j);
}
}
}
for(int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (board[i][j]=='O'){
board[i][j]='X';
} else if (board[i][j]=='#')
board[i][j]='O';
}
}
}
//使用dfs将与边界‘O’相连的‘O’都设置为‘#’
public void dfs(char[][] board,int row,int column){
if (row<0 || row>=m||column<0 || column>=n||board[row][column]!='O'){
return;
}
board[row][column]='#';
for (int d[]:direction){
dfs(board,row+d[0],column+d[1]);
}
}
五、417. 太平洋大西洋水流问题
5.1 题目描述
给定一个 m x n 的非负整数矩阵来表示一片大陆上各个单元格的高度。“太平洋”处于大陆的左边界和上边界,而“大西洋”处于大陆的右边界和下边界。(难度中等)
规定水流只能按照上、下、左、右四个方向流动,且只能从高到低或者在同等高度上流动。
请找出那些水流既可以流动到“太平洋”,又能流动到“大西洋”的陆地单元的坐标。
提示:输出坐标的顺序不重要,m 和 n 都小于150
5.2 代码
5.2.1 DFS
此题与130相似,先处理边界。
思路:如果我们从每一个地方dfs,那么时间复杂度就会很大。因此我们可以反过来想从大洋往上流。这样对于两个大洋,我们从其对应的边界开始遍历,将从大洋可以到达的点标记。完成后,只需遍历一下标记矩阵,满足条件的位置就是两个大洋都能到达的位置。
标记矩阵:canReach[r][c]
dfs,刚开始调用,标记的都是边界,之后满足条件的才调用dfs,因此标记矩阵标记的都是大洋可达的点。
/**
* 如果到达了左和上代表到了太平洋i=0||j=0
* 如果到了右和下说明可以到大西洋i=m-1||j=n-1
**/
private int m, n;
private int[][] direction = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
public List<List<Integer>> pacificAtlantic(int[][] heights) {
//List<List<Integer>> ret = new ArrayList<>();
List<List<Integer>> ret =new LinkedList<>();//两种列表均可
m = heights.length;
n = heights[0].length;
if (heights == null || m == 0) {
return ret;
}
boolean[][] canReachP = new boolean[m][n];
boolean[][] canReachA = new boolean[m][n];
//从大洋的边开始遍历
for (int i = 0; i < m; i++) {
dfs(i, 0, canReachP,heights);//左侧
dfs(i, n - 1, canReachA,heights);//右侧
}
for (int i = 0; i < n; i++) {
dfs(0, i, canReachP,heights);//上侧
dfs(m - 1, i, canReachA,heights);//下侧
}
效率不高,不能用else if,因为存在[m-1][0] 左下角,和两河都相连
// for (int i = 0; i < m; i++) {
// for (int j = 0; j < n; j++) {
// // 从边缘o开始搜索
// boolean isEdgeA = (i == m-1 || j == n-1 );
// boolean isEdgeP = (i == 0 || j == 0);
// if (isEdgeA) {
// dfs(i, j,canReachA,heights);//访问与边界相连的O,全部标记为T
// }
// if (isEdgeP) {
// dfs(i, j,canReachP,heights);//访问与边界相连的O,全部标记为T
// }
// }
// }
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (canReachP[i][j] && canReachA[i][j]) {//canReachA[i][j]记录的是[i][j]能否到达A
ret.add(Arrays.asList(i, j));//Arrays.asList,该方法是将数组转化为list
//列表的第一个元素是i,第二个是j
}
}
}
return ret;
}
//从大洋往上流,将可以到达的点标记为可达
private void dfs(int r, int c, boolean[][] canReach,int[][] heights) {
if (canReach[r][c]) {
return;
}
canReach[r][c] = true;//为了不重复搜索
for (int[] d : direction) {
int nextR = d[0] + r;
int nextC = d[1] + c;
if (nextR >= 0 && nextR < m && nextC >= 0 && nextC < n
&& heights[r][c] <=heights[nextR][nextC]) {
//因为是倒流,因此下次访问的高度要大于现在高度的
dfs(nextR, nextC, canReach,heights);
}
}
}