200. 岛屿数量

搜索 · 07-02

题目链接:https://leetcode.cn/problems/number-of-islands/description/

我的思路

刚接触搜索算法,有点不知所措,拿到这道题也是不知道该咋办,依旧是看了题解才写出来的。

思路

假设你是一个冒险家,开始探索这片土地,你的规则是只能上下左右走,而不能斜着走。现在你要判断这片土地上有多少个独立的不直接相连的岛屿。那么你可以边走边做标记,首先遍历网格,依次作为起点,如果遇到元素为1,说明你登上了一个新的岛,为了防止漏探索或重复探索,你先把这个位置标记为2,表示已经探索过了,接着尝试往上下左右走,看是否还是这个岛的范围,依次重复这个步骤,假设往上走一步,发现还是1,那就再把这里标记为2,然后再尝试从这个位置起上下左右走,直到把这个岛的所有区域都标记为2,则意味着探索完了这个岛

探索完某个岛的条件是,你从这个岛的最后一个位置出发,上下左右走都已经是2或者是0,表示这个岛已经被探索完了。同时在这个过程中,还有可能触及到你探索区域的边界(Gird的尺寸),这时候也意味着这个岛被探索完了

以上思路被称作深度优先搜索算法(Depth-First Search),是一种递归算法,以上问题的实现办法为:

  1. 定义dfs函数,传入一个坐标(x, y),主函数中遍历grid,只要遇到1元素,说明登上了一个新岛,转入到dfs函数中开始探索这个岛,直到探索完
  2. 因为登上了这个岛,登岛的位置也是属于这个岛的,因此dfs函数中要首先将这个位置进行标记,表示已经探索过了,gridx = ‘2’(注意,这里有两个坑,一是这是一个列表形式存储的网格,表示坐标要用索引的索引,不能思维定势写成了[x, y],二是这道题中岛屿标记是用字符串’1’来代表的,而不是数字1,不能搞混了),接着以这个点为参考,上下左右都走一遍,判断:1. 是否有触及到边界,如果其中有位置已经在边界了,那自然这个位置不需要再探索;2. 走到的新位置是否标记为‘1’,即仍然是这个岛的范围,如果是,就再把这个点作为新的参考点进行判断,将这个位置传入到dfs函数中,先标记为2,然后重复这一步;上下左右分别代表了坐标[x, y + 1], [x, y - 1], [x - 1, y], [x + 1, y]
  3. 如何判断这四个位置及其各自的相邻上下左右是否属于这个岛的范围呢?很简单,把这四个坐标都传进dfs进行递归判断就好了,如果触及到边界条件,或者新的位置已经为‘2’或者‘0’,则返回一个空值,说明这个位置及其邻近位置都已经探索完了,同时函数内嵌函数保证了能够遍历完所有相邻可能的上下左右位置,判断语句为:if x < 0 or x >= m or y < 0 or y >= m or grid[x][y] != '1': return 返回一个空值
  4. 主函数中采用enumerate生成每个元素的位置和对应值,因为我们在dfs函数中设置了对于已经探索的位置标记为‘2’,也就意味着只要我们登上了一个岛,dfs就能保证把这个岛给全部探索完,因此,在我们遍历grid的过程中,只有遇到一个新的‘1’时,才将坐标传入到dfs函数中,这表示找到了一个与前面的岛非直接相连的新岛

类推

695 岛屿的最大面积 为这道题的改编版,这下我们不是要判断有几个岛屿了,而是输出最大的那个岛屿的面积,这次grid中的岛屿和海是用数字的形式存储的了,方便我们直接进行计算

题目的形式一模一样,只是要求的量不一样了,思路完全一致,照搬上面的“标记法”即可,同样是做上下左右的尝试,直到越界或到0,这里我们的标记要定为0才行,定为2的话会被计算进去,同时dfs函数返回的是岛屿的面积值,这次不是返回一个空值了(原题目中我们是要靠dfs来把这个岛走完并且将grid中走过的位置都进行标记修改,因此不需要返回任何值,改变grid就好了)

130 被围绕的区域 逆向DFS,找到所有被 X 围绕的区域不容易,但是其等价于找到所有没有没有被 X 围绕的区域(连接边界的区域),这样就可以从边界上的 O 开始进行深度优先搜索,因为边界上的 O 很特殊,与之相连的所有 O 无论如何都不能被围绕,因此可以先从这部分入手,先把他们替换成一个标记,比如 #,最后board中剩余的 O 都是能够被包围的,把它们替换成 X,再把 # 替换回 O,就得到了我们要的答案

反思

  1. 今天看到一个评论,说衡量算法好坏不要用耗时和内存来评判,更不要看超越了多少百分比,因为不同编程语言之间的耗时和内存占用都是不一样的,同时总的耗时内存占用大可能是因为某个特别变态的测试案例所导致的,因此直接用全局的算法复杂度来评判,会更加客观和合理

结果

时间复杂度:$O(mn)$

空间复杂度:$O(mn)$

已是最优解