[LeetCode]-DFS-1

前言

DFS/BFS这块的参考
DFS,Depth First Search,深度优先搜索,是图论中一个遍历搜索算法

200.岛屿数量

在刚开始学习数据结构图论中的DFS时,是使用邻接数组存储结构,借助visited数组标记某个顶点是否被访问过,然后对图中每一个未被访问的节点进行DFS深度优先遍历,每遍历到一个顶点就标记该节点被访问,当所有顶点均被访问,即visited数组中所有元素值都为1时,遍历结束
而在实际解题应用中,有时候可以不用借助visited数组来达到标记被访问的效果,节省空间。例如这道题中,题目说到岛屿总是被水包围,所以只要遍历到了"1",说明一定会对应地存在一个岛屿,那么跟这个"1"在同一个岛屿上的其它"1"就不需要被遍历了,所以对这个"1"进行DFS将与其在同一个岛屿上的“1”改为"0"即可,后续就不会再对那些点进行遍历,达到标记的目的

public int numIslands(char[][] grid) {
    int res = 0;
    for(int i = 0;i < grid.length;i++){
        for(int j = 0;j < grid[0].length;j++){
            if(grid[i][j] == '1'){
                res++;
                dfs(grid,i,j);
            }
        }
    }
    return res;
}
public void dfs(char[][] grid,int i,int j){
	//递归边界 : 索引越界或遍历到了"0"
    if(i < 0 || i >= grid.length || j < 0 || j >= grid[0].length || grid[i][j] == '0') return;
    //标记为"0"
    grid[i][j] = '0';
    //根据"每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成",对上下左右的相邻顶点进行dfs
    dfs(grid,i - 1,j);
    dfs(grid,i + 1,j);
    dfs(grid,i,j + 1);
    dfs(grid,i,j - 1);
}

1254.统计封闭岛屿的数量

从题意可以看出,只要存在在矩阵的四条边上的土地的岛屿都不是封闭岛屿,那么继续沿用上面200题的思想解题,而不同的地方在于,要先把矩阵四条边上的土地所在的岛屿先置为"1",变为水,然后再对剩下的区域进行查找岛屿数量的操作

public int closedIsland(int[][] grid) {
    int row = grid.length;
    int column = grid[0].length;
    int res = 0;
    //先排除掉四条边上的岛屿
    for(int i = 0;i < row;i++){
        dfs(grid,i,0);
        dfs(grid,i,column - 1);
    }
    for(int i = 0;i < column;i++){
        dfs(grid,0,i);
        dfs(grid,row - 1,i);
    }
    //再进行200题的查找岛屿数量操作
    for(int i = 0;i < row;i++){
        for(int j = 0;j < column;j++){
            if(grid[i][j] == 0){
                res++;
                dfs(grid,i,j);
            }
        }
    }
    return res;
}
public void dfs(int[][] grid,int i,int j){
    if(i < 0 || i >= grid.length || j < 0 || j >= grid[0].length || grid[i][j] == 1) return;
    grid[i][j] = 1;
    dfs(grid,i - 1,j);
    dfs(grid,i + 1,j);
    dfs(grid,i,j + 1);
    dfs(grid,i,j - 1);
}

695.岛屿的最大面积

同样的,继续沿用200的做法,而 不同的地方在于,找出岛屿的同时还要记录岛屿的面积,即土地数,所以要把dfs方法改为能计算岛屿面积的方法

public int maxAreaOfIsland(int[][] grid) {
    int res = 0;
    for(int i = 0;i < grid.length;i++){
        for(int j = 0;j < grid[0].length;j++){
            if(grid[i][j] == 1){
            	//与200题相比,不是每dfs一个岛屿就记录一个数量,
            	//而是通过dfs得到岛屿的面积然后记录最大的面积
                res = Math.max(res,dfs(grid,i,j));
            }
        }
    }
    return res;
}
public int dfs(int[][] grid,int i,int j){
    if(i < 0 || i >= grid.length || j < 0 || j >= grid[0].length || grid[i][j] == 0) return 0;
    grid[i][j] = 0;
    //递归的思路:要求当前土地(i,j)所在的岛屿的面积,则等于1加上下左右相邻土地的总面积
    return 1 + dfs(grid,i - 1,j) +
               dfs(grid,i + 1,j) +
               dfs(grid,i,j + 1) +
               dfs(grid,i,j - 1);
}

827. 最大人工岛

这道题可以分为两步:
第一步跟 695 题.岛屿的最大面积 一样,dfs 整个矩阵,找出每个岛屿,并计算每个岛屿的面积
第二步再次遍历整个矩阵,对于每一个海洋 0,找出其上下左右是否有岛屿,若有,每个岛屿的面积又是多少,然后把这块海洋变为陆地,再加上其上下左右的岛屿的面积,得到的结果就是将这块海洋变为陆地所能得到的岛屿的面积
对于上面计算得到的每个岛屿的面积,最大的一个就是整道题的答案

与 695 题 不一样的是,由于第二步我们还要利用到原来矩阵中的海洋,所以在第一步的 dfs 中,我们不能把遍历到的陆地 1 变为 海洋 0,而是应该改为 0 跟 1 之外的其它数字;而且,考虑到每块海洋的上下左右可能邻接着的陆地属于是同一个岛屿上的,所以对于上下左右所邻接的岛屿需要去重
所以在第一步中遍历到的每个岛屿我们应该给一个不同的编号,然后把每个岛屿中遍历到的陆地 1 改为这个编号即可。对于这个编号,由于 0 表示 陆地,1 表示海洋,我们只需从 2 开始,每遍历完一个岛屿,就让编号加一去表示下一个岛屿,即可

class Solution {
    public int largestIsland(int[][] grid) {
        if (grid == null || grid.length == 0) return 1;
        int res = 0;    //最终答案
        int index = 2;  //岛屿编号
        HashMap<Integer, Integer> indexAndAreas = new HashMap<>(); //记录岛屿编号以及岛屿面积
        //开始第一步dfs找出岛屿
        for (int i = 0; i < grid.length; i++) {
            for (int j = 0; j < grid[0].length; j++) {
                if (grid[i][j] == 1) {
                    int area = dfs(grid, i, j, index);
                    indexAndAreas.put(index, area);
                    index++; //编号自增去表示下一个岛屿
                    res = Math.max(res, area);
                }
            }
        }
        //如果res还是等于0说明没有找到任何一个岛屿,直接返回1,即随便找一块海洋将其置为陆地
        if (res == 0) return 1;
        //开始第二步,找每块海洋以及周围岛屿
        for (int i = 0; i < grid.length; i++) {
            for (int j = 0; j < grid[0].length; j++) {
                if (grid[i][j] == 0) {
                    HashSet<Integer> hashSet = findNeighbour(grid, i, j);
                    if (hashSet.size() < 1) continue;  //周围没有岛屿则跳过
                    //周围有岛屿则计算将该块海洋置为陆地后与周围岛屿面积相加得到的整个岛屿面积
                    int tmp = 1;
                    for (Integer k : hashSet) tmp += indexAndAreas.get(k);
                    res = Math.max(res, tmp);
                }
            }
        }
        return res;
    }
    private HashSet<Integer> findNeighbour(int[][] grid, int i, int j) {
        HashSet<Integer> hashSet = new HashSet<>();
        if (checkIndex(grid, i - 1, j) && grid[i - 1][j] > 0) hashSet.add(grid[i - 1][j]);
        if (checkIndex(grid, i + 1, j) && grid[i + 1][j] > 0) hashSet.add(grid[i + 1][j]);
        if (checkIndex(grid, i, j - 1) && grid[i][j - 1] > 0) hashSet.add(grid[i][j - 1]);
        if (checkIndex(grid, i, j + 1) && grid[i][j + 1] > 0) hashSet.add(grid[i][j + 1]);
        return hashSet;
    }
    private int dfs(int[][] grid, int i, int j, int index) {
        if (!checkIndex(grid, i, j) || grid[i][j] != 1) return 0;
        grid[i][j] = index;
        return 1 + dfs(grid, i - 1, j, index) 
                 + dfs(grid, i + 1, j, index) 
                 + dfs(grid, i, j - 1, index) 
                 + dfs(grid, i, j + 1, index);
    }
    //检查下标是否合法
    private boolean checkIndex(int[][] grid, int i, int j) {
        return i >= 0 && i < grid.length && j >= 0 && j < grid[0].length;
    }
}

1020.飞地的数量

这道题结合了1254题跟695题,要先像695题一样排除掉边界的岛屿,然后统计剩下的所有符合"飞地"条件的岛屿的单元格的总数量

public int numEnclaves(int[][] grid) {
    int row = grid.length;
    int column = grid[0].length;
    int res = 0;
    for(int i = 0;i < row;i++){
        dfs(grid,i,0);
        dfs(grid,i,column - 1);
    }
    for(int i = 0;i < column;i++){
        dfs(grid,0,i);
        dfs(grid,row - 1,i);
    }
    for(int i = 0;i < row;i++){
        for(int j = 0;j < column;j++){
            if(grid[i][j] == 1){
            	//计算每个“飞地“的面积,即单元格数量,然后叠加得到最终的总单元格数量
                res += dfs(grid,i,j);
            }
        }
    }
    return res;
}
//统计一个岛屿上的单元格的数量,即一个岛屿的面积
public int dfs(int[][] grid,int i,int j){
    if(i < 0 || i >= grid.length || j < 0 || j >= grid[0].length || grid[i][j] == 0) return 0;
    grid[i][j] = 0;
    return 1 + dfs(grid,i - 1,j) +
            dfs(grid,i + 1,j) +
            dfs(grid,i,j + 1) +
            dfs(grid,i,j - 1);
}

1905.统计子岛屿

题目要求子岛屿的数量,那就先把grid2中不符合子岛屿条件的岛屿先“淹没掉”,即都变成“0“,那么剩下的岛屿就都是子岛屿了,再进行统计岛屿数量的操作就可以了

public int countSubIslands(int[][] grid1, int[][] grid2) {
    int row = grid2.length;
    int column = grid2[0].length;
    for(int  i = 0;i < row;i++){
        for(int j = 0;j < column;j++){
        	//先排除掉不符合子岛屿条件的岛屿
            if(grid2[i][j] == 1 && grid1[i][j] == 0){
                dfs(grid2,i,j);
            }
        }
    }
    //像200题一样统计岛屿数量
    int res = 0;
    for(int  i = 0;i < row;i++){
        for(int j = 0;j < column;j++){
            if(grid2[i][j] == 1){
                res++;
                dfs(grid2,i,j);
            }
        }
    }
    return res;
}
public void dfs(int[][] grid,int i,int j){
    if(i < 0 || i >= grid.length || j < 0 || j >= grid[0].length || grid[i][j] == 0) return;
    grid[i][j] = 0;
    dfs(grid,i - 1,j);
    dfs(grid,i + 1,j);
    dfs(grid,i,j + 1);
    dfs(grid,i,j - 1);
}

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

根据 参考题解 整理:
直接判断一个点能否既能到达太平洋又能到达大西洋可能比较复杂

所以可以分成两步,先找出所有能到达太平洋的点,再找出能到达大西洋的点,这两个点集的交集就是符合题意的点集

在找出哪些点能到海洋时,我们不是直接对每一个点进行判断,看其是否能到达海洋,而是 逆过来,从边界出发,判断从边界出发往岛内能到达哪些点,这些点自然也就能到达海洋

public List<List<Integer>> pacificAtlantic(int[][] heights) {
    List<List<Integer>> res = new ArrayList();
    int m = heights.length,n = heights[0].length;
    //维护记录某个点能否到达太平洋以及大西洋
    boolean canReachP[][] = new boolean[m][n],canReachX[][] = new boolean[m][n];
    //从左右边界出发能到达哪些点
    for(int i = 0;i < m;i++){
        dfs(heights,canReachP,i,0);
        dfs(heights,canReachX,i,n - 1);
    }
    //从上下边界出发能到达哪些点
    for(int j = 0;j < n;j++){
        dfs(heights,canReachP,0,j);
        dfs(heights,canReachX,m - 1,j);
    }
    //一个点只有既能到达太平洋又能到达大西洋才符合题意
    for(int i = 0;i < m;i++){
        for(int j = 0;j < n;j++){
            if(canReachP[i][j] && canReachX[i][j]){
                res.add(Arrays.asList(i,j));
            }
        }
    }
    return res;
}
public void dfs(int[][] heights,boolean[][] canReach,int i,int j){
    //如果已经访问过可达就不用再次访问
    if(canReach[i][j]) return;
    //只要能被访问到,就说明一定能到达海洋
    canReach[i][j] = true;
    //判断上下左右是否有条件继续搜索
    if(i - 1 >= 0 && heights[i - 1][j] >= heights[i][j]) dfs(heights,canReach,i - 1,j);
    if(j - 1 >= 0 && heights[i][j - 1] >= heights[i][j]) dfs(heights,canReach,i,j - 1);
    if(i + 1 < heights.length && heights[i + 1][j] >= heights[i][j]) dfs(heights,canReach,i + 1,j);
    if(j + 1 < heights[0].length && heights[i][j + 1] >= heights[i][j]) dfs(heights,canReach,i,j + 1);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值