Leetcode 刷题(13)队列和堆栈应用实例:求岛屿数量(三种解法)

题目

200. 岛屿数量

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

难度中等
题目分析:对于岛屿的定义是,周围被一圈 “0” 包围,观察例子可以发现,在扫描矩阵的时候,只要发现一个“1”, 就能确定找到一个岛屿。这道题的关键在于,如何把找到的“1”周围相连的“1”都找出来,并进行标记,从而跟另外可能存在的岛屿分开。 基于一个已有的点,向四周搜索的过程,固定有两种策略,一个是深度优先搜索(DFS), 一个是广度优先搜索 (BFS)。

解法一:深度优先搜索 DFS

class Solution:
    
    def isLand(self, grid, i, j):
        return grid[i][j] == "1"
        
    def marked(self, grid, i, j):
        grid[i][j] = "2"
        
    def numIslands(self, grid: List[List[str]]) -> int:
    # 深度优先的解法 DFS
        '''
        采用列表作为栈,尾端就是栈顶
        
        解法策略是,先按行列按顺序扫描,如果发现一个1,那就说明有一个岛
        
        通过 DFS 策略,把整个小岛的1都变成探索过,这是为了避免干扰寻找下一个岛
        
        '''
        if len(grid) == 0:
            return 0  # 矩阵
        
        row = len(grid)
        col = len(grid[0])
        
        num = 0 # 岛屿数
        dirs = [(0, 1), (1, 0), (0, -1), (-1, 0)] # 往右和往下探索
        
        for i in range(row):
            for j in range(col):
                if self.isLand(grid, i, j): # 发现小岛
                    num += 1
                    self.marked(grid, i, j)
                    
                    stack = [((i, j), 0)]  # 存入坐标和下一个探索方向
                    while stack:
                        cur, d = stack.pop()
                        for k in range(d, 4): # 探索方向
                            # 探索方向要求界内
                            if (cur[0] + dirs[k][0] < row) and (cur[1] + dirs[k][1] < col) and \
                                (cur[0] + dirs[k][0] >= 0) and (cur[1] + dirs[k][1] >= 0):
                                nxt = cur[0] + dirs[k][0], cur[1] + dirs[k][1]
                                if self.isLand(grid, *nxt):
                                    self.marked(grid, *nxt)
                                    stack.append((cur, k + 1))  # 存入 cur 位置当前还未探索的位置
                                    stack.append((nxt, 0))
                                    break # 去探索下一个位置
        return num

运行结果:

在这里插入图片描述
在这里插入图片描述

解法二: 深度优先搜索 DFS, 基于递归


class Solution:
    
    def isLand(self, grid, i, j):
        return grid[i][j] == "1"
        
    def marked(self, grid, i, j):
        grid[i][j] = "2"
        
    def numIslands(self, grid: List[List[str]]) -> int:
    	# 基于递归的DFS
        def DFS(self, grid, i, j):
            '''
            基于送进来的位置,搜索周围4个方向,把"1"都找出来, 所以不需要返回值
            '''
            for k in range(4): # 四个探索方向
                nxt = i + dirs[k][0], j + dirs[k][1]
                if nxt[0] < row and nxt[0] >= 0 and nxt[1] < col and nxt[1]>=0:  # 在界内
                    if self.isLand(grid, *nxt):
                        self.marked(grid, *nxt)
                        DFS(self, grid, *nxt)
        
        if len(grid) == 0: 
            return 0
        
        row = len(grid)
        col = len(grid[0])
        
        num = 0
        dirs = [(0, 1), (1, 0), (0, -1), (-1, 0)] # 往右和往下探索
        
        for i in range(row):
            for j in range(col):
                if self.isLand(grid, i, j): # 找到一个 “1”,一定是个岛
                    num += 1
                    self.marked(grid, i, j)
                    # 把相邻的 “1” 找出来,并消除
                    DFS(self, grid, i, j)
                    
                    
        return num

运行结果:

在这里插入图片描述
在这里插入图片描述

要点分析:

  1. 同样是DFS,使用递归比我们自己维持一个栈,速度快了一些!所以,如果要使用DFS,优先用递归实现;
  2. 使用了递归方法后,解法二的代码,比解法一简洁了许多,更好理解
  3. 明确这里的子函数 DFS 作用在于把一整块的"1"消除掉(代码中是将其标记为“2”,可以避免重复探查,又可以跟“0”区分开),因为要判断岛屿,找到一个“1” 就够了;所以 DFS的

解法三: 广度优先搜索 BFS

from collections import deque

class Solution:
    
    def isLand(self, grid, i, j):
        return grid[i][j] == "1"
        
    def marked(self, grid, i, j):
        grid[i][j] = "2"
        
    def numIslands(self, grid: List[List[str]]) -> int:
        # 建立的队列数就是对应的岛屿的个数
        # 发现第一个'1',便建立队列,然后探索右和下两个方向,如果是1,入队,是零,不用
        # 队列走完,就是一个岛
        # 重复循环
        
        # BFS广度优先的解法
        if len(grid) == 0:  # 空列表
            return 0
        
        row = len(grid)
        col = len(grid[0])
        
        temp_queue = deque()
        num = 0
        
        dirs = [(0, 1), (1, 0), (0, -1), (-1, 0)] # 往右和往下探索
        
        # 从第一个格开始,如果是1,就入队,搜索周围的区域是不是1,队列的话,按广度搜索,标记为0;
        for i in range(row):
            for j in range(col):
                if self.isLand(grid, i, j):
                    num += 1
                    self.marked(grid, i, j)  # 标记为探索过
                    
                    temp_queue.append((i, j))
                    
                    while len(temp_queue) != 0:
                        # pos = temp_queue[0] # 此处错在没有删除队首端元素
                        pos = temp_queue.popleft()
                        for k in range(4):
                            if (pos[0] + dirs[k][0] < row) and (pos[1] + dirs[k][1]<col) \
                            and (pos[0] + dirs[k][0] >= 0) and (pos[1] + dirs[k][1]>= 0):
                                nextp = pos[0] + dirs[k][0], pos[1]+dirs[k][1]
                                if self.isLand(grid, nextp[0], nextp[1]):
                                    # 标记,探索过了
                                    self.marked(grid, nextp[0], nextp[1])
                                    # 进队
                                    temp_queue.append((nextp[0], nextp[1]))
                    # while出来,一个小岛的全部陆地就探索完了    
                    
        
        return num

运行结果:

在这里插入图片描述
在这里插入图片描述

要点分析:

基于队列的解法,我们自己维护一个队列,跟基于堆栈的解法,我们维持一个堆栈,非常相似,不过运行时间有明显差别,感觉LeetCode现在的评判系统,计时这部分很不准,还是参考参考就好,不要太纠结。这里我们掌握不同解法的核心思想就好!

总结:

DFSBFS 解法中的异同点:
相同点

  1. 均要维持一个缓存数据结构,递归算法也不例外,因为利用了系统调用的栈
  2. 均要把缓存数据结构里的所有位置探索完,程序才能结束

不同点
在于搜索的顺序不同

深度优先搜索,DFS, 顾名思义,就是一条路走到黑,不撞南墙不回头。于是,每次栈弹出一个位置和一个探查开始方向。我们只要一找到下个可行的方向 nxt (解法一), 就把此刻的位置 cur 和 待探查的方向存入栈 (先不管其他可能方向的意思),然后把 nxt 入栈,这样下一个探查,我们就从nxt这个点继续前进。这里面的核心语句是 break

广度优先搜索,BFS,侧重于“广”, 跟DFS区别在于,到了 cur 点后,把所有可行的探索方向(self.isLand() 输出为“1”的方向)都通通入队,然后,再进入下一个循环,弹出一个探查方向。于是,你可以发现,在这段代码里,没有 break 语句!

举一反三

以后遇到类似问题,解法一 和 解法三 探索的框架保留,具体判断代码替换掉,就可以马上套用。这种类型的题目,框架都是一样的。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值