岛屿问题

2021-12-24  本文已影响0人  肉松饼饼

岛屿系列题目的核心考点就是用 DFS/BFS 算法遍历二维数组。本文分析DFS算法。

一、框架

因为二维矩阵本质上是一幅「图」,所以遍历的过程中需要一个 visited 布尔数组防止走回头路

// 二维矩阵遍历框架
func dfs(grid [][]int, i, j int, visited [][]bool) {
    // 超出索引
    if i < 0 || j < 0 || i >= len(grid) || j >= len(grid[0]) {
        return
    }
    // 已经遍历过(i, j)
    if visited[i][j] {
        return
    }
    // 进入节点(i, j)
    visited[i][j] = true
    dfs(grid, i-1, j) // 上
    dfs(grid, i+1, j) // 下
    dfs(grid, i, j-1) // 左
    dfs(grid, i, j+1) // 右
}

二、真题

200. 岛屿数量

给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。此外,你可以假设该网格的四条边均被水包围。
eg: 如图,岛屿数量为4


image.png
func numIslands(grid [][]byte) int {
    var result int
    for i := 0; i < len(grid); i++ {
        for j := 0; j < len(grid[0]); j++ {
            if grid[i][j] == '1' {
                result++
                dfs(grid, i, j)
            }
        }
    }
    return result
}

// 从(i, j)开始,将与之相邻的陆地都变成海水
func dfs(grid [][]byte, i, j int) {
    // 超出索引边界
    if i < 0 || j < 0 || i >= len(grid) || j >= len(grid[0]) {
        return
    }
    // 已经是海水了
    if grid[i][j] == '0' {
        return
    }
    // 将 (i, j) 变成海水
    grid[i][j] = '0'
    // 淹没上下左右的陆地
    dfs(grid, i-1, j)
    dfs(grid, i+1, j)
    dfs(grid, i, j-1)
    dfs(grid, i, j+1)
}

为什么每次遇到岛屿,都要用 DFS 算法把岛屿「淹了」呢?主要是为了省事,避免维护 visited 数组。因为 dfs 函数遍历到值为 0 的位置会直接返回,所以只要把经过的位置都设置为 0,就可以起到不走回头路的作用。

PS:这类 DFS 算法还有个别名叫做 [FloodFill 算法]

1254. 统计封闭岛屿的数目

有一个二维矩阵 grid ,每个位置要么是陆地(记号为 0 )要么是水域(记号为 1 )。我们从一块陆地出发,每次可以往上下左右 4 个方向相邻区域走,能走到的所有陆地区域,我们将其称为一座「岛屿」。如果一座岛屿 完全 由水域包围,即陆地边缘上下左右所有相邻区域都是水域,那么我们将其称为 「封闭岛屿」。请返回封闭岛屿的数目。
eg: 如下图,封闭岛屿数量为2


image.png

思路:把靠边的岛屿排除掉,剩下的就是「封闭岛屿」

func closedIsland(grid [][]int) int {
    n, m := len(grid), len(grid[0])
    for j := 0; j < m; j++ {
        // 把靠上边的岛屿淹掉
        dfs(grid, 0, j)
        // 把靠下边的岛屿淹掉
        dfs(grid, n-1, j)
    }
    for i := 0; i < n; i++ {
        // 把靠左边的岛屿淹掉
        dfs(grid, i, 0)
        // 把靠右边的岛屿淹掉
        dfs(grid, i, m-1)
    }
    var result int
    for i := 0; i < n; i++ {
        for j := 0; j < m; j++ {
            if grid[i][j] == 0 {
                result++
                dfs(grid, i, j)
            }
        }
    }
    return result
}

func dfs(grid [][]int, i, j int) {
    // 超出索引边界
    if i < 0 || j < 0 || i >= len(grid) || j >= len(grid[0]) {
        return
    }
    // 已经是海水了
    if grid[i][j] == 1 {
        return
    }
    // 将 (i, j) 变成海水
    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. 岛屿的最大面积

思路:在 dfs 函数淹没岛屿的同时,记录这个岛屿的面积,即 dfs 函数设置返回值,记录每次淹没的陆地的个数。

func maxAreaOfIsland(grid [][]int) int {
    var maxArea int
    for i := 0; i < len(grid); i++ {
        for j := 0; j < len(grid[0]); j++ {
            if grid[i][j] == 1 {
                maxArea = max(maxArea, dfs(grid, i, j))
            }
        }
    }
    return maxArea
}

func dfs(grid [][]int, i, j int) int {
    // 超出索引边界
    if i < 0 || j < 0 || i >= len(grid) || j >= len(grid[0]) {
        return 0
    }
    // 已经是海水了
    if grid[i][j] == 0 {
        return 0
    }
    // 将 (i, j) 变成海水
    grid[i][j] = 0
    // 淹没上下左右的陆地
    return dfs(grid, i, j-1) + dfs(grid, i, j+1) + dfs(grid, i-1, j) + dfs(grid, i+1, j) + 1
}

func max(a, b int) int {
    if a > b {
        return a
    }
    return b
}

1905. 统计子岛屿

eg:如下图,子岛屿数量为红色部分,3个

image.png

思路:当岛屿 B 中所有陆地在岛屿 A 中也是陆地的时候,岛屿 B 是岛屿 A 的子岛。反过来说,如果岛屿 B 中存在一片陆地,在岛屿 A 的对应位置是海水,那么岛屿 B 就不是岛屿 A 的子岛。那么,我们只要遍历 grid2 中的所有岛屿,把那些不可能是子岛的岛屿排除掉,剩下的就是子岛。

func countSubIslands(grid1 [][]int, grid2 [][]int) int {
    // 岛屿2中存在一片陆地,在岛屿1的对应位置是海水,那么岛屿2就不是岛屿1的子岛
    for i := 0; i < len(grid2); i++ {
        for j := 0; j < len(grid2[0]); j++ {
            if grid1[i][j] == 0 && grid2[i][j] == 1 {
                dfs(grid2, i, j)
            }
        }
    }

    var result int
    for i := 0; i < len(grid2); i++ {
        for j := 0; j < len(grid2[0]); j++ {
            if grid2[i][j] == 1 {
                result++
                dfs(grid2, i, j)
            }
        }
    }
    return result
}

func dfs(grid [][]int, i, j int) {
    // 超出索引边界
    if i < 0 || j < 0 || i >= len(grid) || j >= len(grid[0]) {
        return
    }
    // 已经是海水了
    if grid[i][j] == 0{
        return
    }
    // 将 (i, j) 变成海水
    grid[i][j] = 0
    // 淹没上下左右的陆地
    dfs(grid, i-1, j)
    dfs(grid, i+1, j)
    dfs(grid, i, j-1)
    dfs(grid, i, j+1)
}
上一篇下一篇

猜你喜欢

热点阅读