Leetcode 200. 岛屿数量
深度优先搜索法:
对于这道题来说,是一个非常经典的图的问题,我们可以先从宏观上面来看问题,也就是说在不想具体算法的前提下,简单的说出如何找到所有的岛屿呢?
如图中所示,首先非常显而易见的要素就是我们一定要遍历图中的每一个坐标,这是肯定的,然后呢对于岛屿的数量,一开始想的肯定就是每当我们访问了一个1的坐标时,我们就找到了一个岛屿,可是因为我们要遍历整个图,所以肯定不能每次遇到一个1就对岛屿的数量进行增加,因为题目说了,横纵连接着的坐标都是1的话都属于同一个岛屿,就像上图一样证明我们只有三个岛屿
那么我们是不是可以像一个办法,当我们找到了一个岛屿的坐标后,把属于这个坐标的岛屿的其他所有值为1的坐标都记录下来,这样以来,意思就是说在我们继续遍历的时候,我们检查每一个坐标时,如果发现这个坐标已经被标记过了,证明他并不是一个新的岛屿而是一个之前就发现过的岛屿的一部分那么就可以了
如何去实现这个标记的方法呢,就可以用到我们的深度优先搜索,从新发现的坐标开始一直找,直到找到了岛屿的边缘或者整个图的边缘之后返回,这样就能找到所有属于相同岛屿的坐标,那么我们可以把主代码写出来了已经
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
res = 0
# iterate to check every box in the grid to see
# if it is a start for a new lsland
for r in range(len(grid)):
for c in range(len(grid[0])):
# if so, run a dfs to mark all its fellow
# box and add the 1 to the result
if grid[r][c] == '1':
dfs(grid, r, c)
res = res + 1
return res
主体部分的代码非常简单,然后呢我们需要做的就是对于dfs的编写,正如在这个主体代码中写的,你会发现我这里还是用的如果box中存的元素是1,那就最终要增加岛屿数量,这难道不是错的吗?其实这里就隐喻了我们是如何完成对一个岛屿上所有的box进行标记的,与其新建一个空间例如集合然后把dfs中搜索到的box全都加入进去然后每一次循环遍历的时候去看当前的box是否在那个集合中,更好的方法其实就是直接原地更改我们的grid,将搜索到的box中的值从1修改到2,这样这个主体函数中的if 语句就不会被execute了
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
def dfs(grid, r, c):
# base case, do nothing when we
# reached the edge of entire grid or found water
if not (0 <= r < len(grid) and 0 <= c < len(grid[0])) or grid[r][c] != '1':
return
# mark the visited box as 2
grid[r][c] = '2'
# call the recursive case on each direction from current box
dfs(grid, r - 1, c)
dfs(grid, r + 1, c)
dfs(grid, r, c - 1)
dfs(grid, r, c + 1)
res = 0
# iterate to check every box in the grid to see
# if it is a start for a new lsland
for r in range(len(grid)):
for c in range(len(grid[0])):
# if so, run a dfs to mark all its fellow
# box and add the 1 to the result
if grid[r][c] == '1':
dfs(grid, r, c)
res = res + 1
return res
这样你就有了整个的代码,其实这种类型的深度优先搜索可以参考我们在二叉树的时候是怎么做的,无非也是一直遍历然后直到我们reach 到了leaf node然后发现新的node是空的时候就直接return,这里不过是多加了两个方向,在二叉树里的recursive case是对左右子节点继续call,这里是对上下左右四个横纵坐标的方向继续call,然后呢条件多加了一个,这里对应的不能超出边界就是二叉树中的if not root, 只是这里还要保证grid[r][c] == 1,不是1的话就不是我们要找的同一个岛屿上的点
时间复杂度 O(m*n):m和n就是grid的长和宽,因为我们要遍历每一个坐标
空间复杂度 O(m*n):在最坏的情况下,如果左上角的box就是陆地1,并且整张图都是1, 那么我们就要把每一个box都有一个recursive function,这样在就会用 到一个大小为m*n的栈
Leetcode 994. 腐烂的橘子
广度优先搜索法:
这道题的关键在于我们在一开始所给的grid里面存在多个橘子的可能,我们需要在把能弄腐烂的橘子全部弄完之后就返回结果,所以每一分钟里我们需要对所有当前的橘子进行扩散,这里运用深度优先的递归就很麻烦,因为我们每一次逻辑要处理多个搜索
这里我们就使用广度优先,利用队列来对腐烂橘子进行保存并且通过出队来进行遍历然后扩散后把新的腐烂的橘子继续加入队列
这样以来,每一次我们通过向四个方向的检查中发现新的好橘子之后就可以立马对其进行操作,具体分三步,第一步将其加入我们的队列代表下一轮遍历的时候,他会pop出来作为新的坐标然后向四周扩散,第二步将我们的好橘子的count做一个递减,第三步,将grid中其坐标位置的值从1变成2避免后序的遍历中反复visit这个坏橘子
class Solution:
def orangesRotting(self, grid: List[List[int]]) -> int:
# we are using a queue to do the bfs
q = deque()
fresh_count = 0
res = 0
# loop through the gird first to find
# the amount of fresh and add the rotten to
# the queue
for i in range(len(grid)):
for j in range(len(grid[0])):
if grid[i][j] == 1:
fresh_count = fresh_count + 1
elif grid[i][j] == 2:
q.append((i, j))
# loop until we have no more rotten orange
while q and fresh_count > 0:
# every time we start pop out q again, we did another search
res = res + 1
# for each rotten orango, pop them out
for _ in range(len(q)):
box = q.popleft()
r, c = box[0], box[1]
# check top
if r - 1 >= 0 and grid[r - 1][c] == 1:
fresh_count = fresh_count - 1
q.append((r - 1, c))
grid[r - 1][c] = 2
# check right
if c + 1 < len(grid[0]) and grid[r][c + 1] == 1:
fresh_count = fresh_count - 1
q.append((r, c + 1))
grid[r][c + 1] = 2
# check bot
if r + 1 < len(grid) and grid[r + 1][c] == 1:
fresh_count = fresh_count - 1
q.append((r + 1, c))
grid[r + 1][c] = 2
# check left
if c - 1 >= 0 and grid[r][c - 1] == 1:
fresh_count = fresh_count - 1
q.append((r, c - 1))
grid[r][c - 1] = 2
# we did not get them all rotton
if fresh_count > 0:
return -1
else:
return res
时间复杂度 O(m*n):m和n就是grid的长和宽,因为我们要遍历每一个坐标
空间复杂度 O(m*n):在最坏的情况下,一开始所有的橘子都是坏橘子,我们使用的额外空间队列的长度最大就是m*n