「这是我参与2022首次更文挑战的第8天,活动详情查看:2022首次更文挑战」。
记录 1 道算法题
岛屿数量
题目介绍
在一个二维数组里面,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)) } } }
结束