网格结构问题集锦
集中记录一下涉及在网格结构上搜索的题目及其各种解法,例如各种「岛屿」问题
前言
这里的网格结构,也可以称为棋盘结构,在其之上做搜索跟图结构很像,本文做一次简单总结
在网格结构上搜索时,有些细节需要注意
以下, 表示网格的纵向长度, 表示网格的横向高度,即网格尺寸
坐标移动
对于任何一点 ,从此出发的搜索方向有四种:上、下、左、右,在代码实现上,目前自己使用的有两种方式
第一种
搜索方向的顺序依次是上、右、下、左
1 | static final int[][] dirs = new int[][]{{-1,0}, {1,0}, {0, 1}, {0, -1}}; |
第二种
改用一维数组,更省空间
1 | static final int[] dirs = new int[]{0, -1, 0, 1, 0}; |
边界控制
保证坐标在合法范围内, ,
1 | boolean valid(int m, int n, int x, int y) { |
访问控制
搜索的时候,可能会搜索到重复(已经遍历过的)的陆地,需要通过一些标识来避免重复访问
- 可以改变网格数组
grid
的值,用2
表示已经遍历过的陆地,则1
表示尚未遍历的陆地,0
表示海洋 - 或者新建一个二维数组
st[m][n]
来保存每个网格的访问状态,这样会有额外开销
如何选取需要结合题目要求具体分析
200. 岛屿数量
给你一个由 '1'
(陆地)和 '0'
(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例
1 | 输入:grid = [ |
简要分析
如果把彼此相连的1
视作一个连通块的话,求岛屿的数量就相当于求网格结构中连通块的数量。这样首先想到的,其实是用并查集去做。
深度优先搜索
DFS采用递归实现,代码比较简短。
1 | class Solution { |
广度优先搜索
BFS同深搜一样,都是从某块陆地出发向四周搜索,直到到达海洋或者边界,一轮结束,则连通块数量加一,即岛屿数量加一
1 | class Solution { |
并查集
并查集天生就是用来求连通块数量这种问题
从某个1
陆地出发,分别向右侧、下方合并相邻的陆地即可
为什么只合并右边和下方的陆地呢?
因为整体的遍历顺序是外层 ,内层 ,每次向右下合并,就可以保证所有的相邻陆地都可以被合并到一起
在合并的过程中,统计遇到的海洋网格数量 oceanCnt
,每将一个新的陆地合并进当前的连通块,总网格数量 allCnt
减一,最终的 allCnt-oceanCnt
就是连通块的数量
代码实现上,一般是用 表示当前坐标 ,这样所有的坐标都可以用一个整数唯一标识,不会冲突
数组模拟的并查集
包含了路径压缩和按秩合并
1 | class Solution { |
java内部类实现的并查集
1 | class Solution { |
827. 最大人工岛
本题属于
200.岛屿数量
的进阶题目
给你一个大小为 n x n
二进制矩阵 grid
。最多 只能将一格 0
变成 1
。
返回执行此操作后,grid
中最大的岛屿面积是多少?
岛屿 由一组上、下、左、右四个方向相连的 1
形成。
示例
1 | 输入: grid = [[1, 0], [0, 1]] |
简要分析
大致思路是,先遍历所有的陆地,得到所有的连通块(岛屿)的大小,并保存下来;再搜索海洋网格,如果相邻周围有不同连通块,则尝试将当前海洋变成陆地,比较新岛屿的面积
- 具体实现上,需要在搜索过程中,记录当前陆地所属连通块的编号
新建一个二维数组
int[][] tag
,存储当前陆地的连通块编号,并且还可以起到访问控制的作用:如果tag[x][y] != 0
说明陆地(x,y)
尚未被访问到为避免含义模糊,对于坐标的整数标识,就需要从 开始计算, , 为网格的横向长度
- 搜索海洋时,需要创建一个临时的哈希集合,来判断相邻的岛屿是否为同一个
下面是代码实现
深度优先搜索
1 | class Solution { |
广度优先搜索
主体代码和深搜没有什么区别,这里主要放出BFS部分代码
1 | int bfs(int[][] grid, int[][] tag, int x, int y, int idx) { |
BFS方案完整代码
1 | class Solution { |
并查集
并查集应该是本题最优解,多增添一个cnt
数组,在合并的时候处理一下,就可以保存每一个连通块的大小,即岛屿的面积
并查集的代码实现
1 | class DSU { |
并查集方案完整代码
1 | class Solution { |
接下来放几道简单的网格结构题目
其他例题
695. 岛屿的最大面积
给你一个大小为 m x n
的二进制矩阵 grid
。
岛屿 是由一些相邻的 1
(代表土地) 构成的组合,这里的「相邻」要求两个 1
必须在 水平或者竖直的四个方向上 相邻。你可以假设 grid
的四个边缘都被 0
(代表水)包围着。
岛屿的面积是岛上值为 1
的单元格的数目。
计算并返回 grid
中最大的岛屿面积。如果没有岛屿,则返回面积为 0
。
示例:
图片来自Leetcode
1 | 输入:grid = [[0,0,1,0,0,0,0,1,0,0,0,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,1,1,0,1,0,0,0,0,0,0,0,0],[0,1,0,0,1,1,0,0,1,0,1,0,0],[0,1,0,0,1,1,0,0,1,1,1,0,0],[0,0,0,0,0,0,0,0,0,0,1,0,0],[0,0,0,0,0,0,0,1,1,1,0,0,0],[0,0,0,0,0,0,0,1,1,0,0,0,0]] |
解题思路跟之前200.岛屿数量如出一辙,不再赘述
DFS方案代码
1 | class Solution { |
BFS方案代码
1 | class Solution { |
并查集方案代码
1 | class Solution { |
463. 岛屿的周长
给定一个 row x col
的二维网格地图 grid
,其中:grid[i][j] = 1
表示陆地, grid[i][j] = 0
表示水域。
网格中的格子 水平和垂直 方向相连(对角线方向不相连)。整个网格被水完全包围,但其中恰好有一个岛屿(或者说,一个或多个表示陆地的格子相连组成的岛屿)。
岛屿中没有“湖”(“湖” 指水域在岛屿内部且不和岛屿周围的水相连)。格子是边长为 1 的正方形。网格为长方形,且宽度和高度均不超过 100 。计算这个岛屿的周长。
示例:
图片来自Leetcode
1 | 输入:grid = [[0,1,0,0],[1,1,1,0],[0,1,0,0],[1,1,0,0]] |
本题其实没必要用深搜,直接数学计算更简单
DFS方案
搜索过程中,遇到海洋,贡献 边长,越过网格边界,也会贡献 个边长,而遇到已经遍历过的陆地,贡献 边长
1 | class Solution { |
只有和海洋网格或者边界相邻的陆地的部分边长,才会构成岛屿的周长
1 | class Solution { |