[路飞] 岛屿数量 —— 二维数组(矩阵)的DFS、BFS

640 阅读4分钟

「这是我参与2022首次更文挑战的第8天,活动详情查看:2022首次更文挑战」。

记录 1 道算法题

岛屿数量

leetcode-cn.com/problems/nu…


题目介绍

在一个二维数组里面,1表示陆地,0表示海洋,岛屿是多个1组成,被海洋包围。比如:下面就是一个小岛 1 1 1

    0 0 1 1 1 0 0
    0 0 0 0 0 0 0

DFS、BFS 介绍

这种立体的二维数组的扫描方法,之所以称之为立体,是跟二叉树相比。DFS和BFS在二叉树中应该是见过很多次的老朋友了。先简单的温习一下二叉树的DFS和BFS。

  • DFS

        function dfs(node) {
            if (!node) return
            
            dfs(node.left)
            dfs(node.right)
        }
    

    每到一个节点就进入他的子节点,直到叶子节点才一层层返回,称为深度优先。

            a
         b     c
    
  • BFS

        function bfs() {
            const queue = [node]
            while(queue.length) {
                const len = queue.length
                for(let i = 0; i < len; i++) {
                    const node = queue.shift()
                    node.left && queue.push(node.left)
                    node.right && queue.push(node.right)
                }
            }
        }
    

    每次 queue 里面都是这一层广度的所有节点。统计总共有多少个节点,然后将这些节点拿出来,然后推入他们的子节点。每次进行这个操作时候,queue 里面都会迭代成下一层广度的所有节点。

              a       queue a
           b     c    queue b,c
        d       f  g  queue d,f,g
    

温习完二叉树的 DFS、BFS 之后,我们来看看二维数组是怎么进行的。首先我们看看二维数组的立体版。

    1 1 1 1 1 1 1 
    1 1 1 1 1 1 1 
    1 1 1 1 1 1 1 
    1 1 1 1 1 1 1
    
    里面的其中一个视角
         b
       c a d
         e
         
    对比二叉树的视角
    
        a
      b   c

我们可以看到二叉树有两个方向 left 和 right。 二维数组是有 4 个方向的分别是上右下左。所以其实和二叉树相比。我们多了两个方向。

比如深度我们需要进入 left 和 right。现在我们需要进入上右下左。

比如广度我们需要往 queue 推入 left 和 right,现在我们需要推入上右下左。

但是我们也看到了二叉树是一个单方向的父与子。但是二维数组更像是兄弟,你能看到我,我能看到你。你的左边是我的右边,我的右边是你的左边。这样一来就会出现重复检查的问题。

那为了解决这个问题需要引入一个集合来记录哪些是已经检查过的。那就不会被重复收集。

二叉树的 left 和 right,在二维数组里就是用下标 arr[r][c] 来代表每个节点。 r 是 row 的缩写代表行,c 是 column 的缩写代表列。

以上就是二维数组的 DFS 和 BFS 的实现思路,下面来根据岛屿数量这道题来进行实操。

结合岛屿数量的代码实现

因为二维数组没有其他用处,所以我们可以通过修改二维数组的值来代替用集合进行标记。比如我们收集陆地 1, 那我们可以将遍历过的 1 改成 0。那就会在遍历的时候把相邻的潜在的 1 都变成 0。直到都是海洋结束这次深度或者广度。

  • DFS

        function numIslands(grid) {
            const width = grid[0].length
            const height = grid.length
            let count = 0
            
            for(let i = 0; i < height; i++) {
                for(let j = 0; j < width; j++) {
                    // 双循环,当遇到陆地的时候就收集和他相邻的陆地,直到没有
                    if (grid[i][j] === '1') {
                        dfs(grid, i, j)
                        count++
                    }
                }
            }
        }
        
        function dfs(grid, i, j) {
            // 上
            if (grid[i]?.[j-1] === '1') {
                grid[i][j-1] = '0'
                dfs(grid, i, j - 1)
            }
            // 右
            if (grid[i+1]?.[j] === '1') {
                grid[i+1][j] = '0'
                dfs(grid, i + 1, j)
            }
            // 下
            if (grid[i]?.[j+1] === '1') {
                grid[i][j+1] = '0'
                dfs(grid, i, j + 1)
            }
            // 左
            if (grid[i-1]?.[j] === '1') {
                grid[i-1][j] = '0'
                dfs(grid, i - 1, j)
            }
        }
    
  • BFS

    BFS 稍微复杂一定用上了数学式子,主要是坐标的转换,我们可以保存成字符串 ${r},${c},或者数组 [r, c],来收集到 queue 里面。如果想更节省空间就要用到数学式子了,r * width + c就是可以代表二维数组的下标,用取余和除法运算,分别得到 c 和 r。

        function numIslands(grid) {
            let height = grid.length
            let width = grid[0].length
            let count = 0
            const queue = []
    
            for (let i = 0; i < height; i++) {
                for (let j = 0; j < width; j++) {
                    if (grid[i][j] === '1') {
                        count++
                        // 收集广度,内部收集相邻的岛
                        queue.push(i * width + j)
                        bfs(grid, queue, width)
                    }
                }
            }
    
            return count
        }
    
        function bfs(grid, queue, width) {
            while (queue.length) {
                const rc = queue.shift()
                // 用了数学式子来存放 r 和 c
                const r = (rc / width) | 0
                const c = rc % width
                // 依然是上右下左,但不再是递归,而是收集到queue。
                if (grid[r + 1]?.[c] === '1') {
                    grid[r + 1][c] = '0'
                    queue.push((r + 1) * width + c)
                }
                if (grid[r]?.[c + 1] === '1') {
                    grid[r][c + 1] = '0'
                    queue.push(r * width + (c + 1))
                }
                if (grid[r - 1]?.[c] === '1') {
                    grid[r - 1][c] = '0'
                    queue.push((r - 1) * width + c)
                }
                if (grid[r]?.[c - 1] === '1') {
                    grid[r][c - 1] = '0'
                    queue.push(r * width + (c - 1))
                }
            }
        }
    

结束