LeetCode 200. 岛屿数量

这篇博客介绍了如何利用深度优先遍历(DFS)、广度优先遍历(BFS)和并查集(Union Find)算法解决计算二维网格中岛屿数量的问题。文中详细阐述了三种方法的实现思路,并提供了具体的代码示例。通过DFS和BFS,遍历网格并将相连的陆地标记为水,从而得出岛屿数量;而并查集方法则通过维护一个一维数组,用集合概念表示岛屿,进行高效合并和查询。
摘要由CSDN通过智能技术生成

1、题目

给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。

岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。

此外,你可以假设该网格的四条边均被水包围。

示例 1:

输入:grid = [
  ["1","1","1","1","0"],
  ["1","1","0","1","0"],
  ["1","1","0","0","0"],
  ["0","0","0","0","0"]
]
输出:1

2、解答

这道题是一道很经典的题目,比较好想到的是DFS和BFS方法。最开始我也想到是这两种解法,看到题解后还学到了并查集的解法,也是很好的一个学习。

  • DFS:深度优先遍历,就是遍历网格时,如果遍历到“1”,则把前后左右的“1”都递归变为“0”。理解一下深度优先遍历的思想,就是一直往更深层次满足某种特征的元素遍历。则岛的个数其实就是开始出发DFS的次数。
  • BFS:广度优先遍历,这个和上面的区别是每次以广度优先,先遍历完某一次满足条件的点,然后再遍历更深层次。这里需要借助队列。
  • 并查集:这个解法的核心思想是用集合中的一个元素代表集合。对于网格这个二维数组来说,可以映射到一个一维数组,这个数组对应的元素保存的值是对应索引元素所在的集合。这个集合可以直接用对应的索引值来表示。这个集合其实就是题目中这个岛屿的概念。
    对于并查集来说,主要分为三个部分:初始化、查询和合并。
例如:
grid = [
  ["1","1","1","1","0"],
  ["1","1","0","1","0"],
  ["1","1","0","0","0"],
  ["0","0","0","0","0"]
]
1、初始化
这是一个4*5的网格,映射的一维数组长度就是4*5.
对于并查集来说维护一个parent数组,长度是4*5,每个元素维护的是该索引位置元素所属的集合编号。
其实换一种说法,这个集合其实就是一棵树,然后这个集合编号就是对应树的根节点。
例如对于grid[0][1]这个元素来说,它映射到parent数组的位置是0*5+1=1。
在最开始的时候,咱们假设每个元素都是一棵树,那么对应的根节点就是它本身。
然后为了后面合并方便,还对应的维护另外一个与parent对应的rank数组,对应的是记录
对应所属子树的复杂度,初始都是1或者0都行。

2、查询
查询指的是查询某个元素所属子树的根节点。代码实现很简单。当然这个还有一些细节,例如如何进行查询路径压缩。
例如查询索引位置为X的根节点,要么parent[x]==x,
要么不等于就递归查询parent[parent[x]].

3、合并
合并X和Y两个元素,先查询他俩对应的根节点是否一致,
一致不用管,
不一致的话,查看对应子树的复杂度,
把复杂度低的合并到复杂度高的上去;
复杂度一致的时候,把X合并到Y上,然后把Y的复杂度加一。

该方法学习强烈推荐学习:https://zhuanlan.zhihu.com/p/93647900/。

下次专门挑几道题目来学习一下这个算法。

class Solution(object):
    def numIslands(self, grid):
        """
        dfs:深度优先搜索,递归搜索将为连成岛屿的1全部变成0
        :type grid: List[List[str]]
        :rtype: int
        """
        row = len(grid)
        if row == 0:
            return 0
        col = len(grid[0])
        nums = 0
        for i in range(row):
            for j in range(col):
                if grid[i][j] == "1":
                    nums += 1
                    self.dfs(grid, i, j, row, col)
        return nums

    def dfs(self, grid, x, y, row, col):
        grid[x][y] = "0"
        for i, j in [(x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)]:
            if i > row - 1 or j > col - 1 or i < 0 or j < 0:
                continue
            if grid[i][j] == "1":
                self.dfs(grid, i, j, row, col)

    def numIslands2(self, grid):
        """
        bfs:广度优先遍历,需要借助队列来遍历
        :type grid: List[List[str]]
        :rtype: int
        """
        row = len(grid)
        if row == 0:
            return 0
        col = len(grid[0])
        nums = 0
        queue = []
        for i in range(row):
            for j in range(col):
                if grid[i][j] == "1":
                    nums += 1
                    grid[i][j] = 0
                    queue.append([i, j])
                while len(queue) > 0:
                    x, y = queue.pop(0)
                    for cur_x, cur_y in [(x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)]:
                        if 0 <= cur_x <= row - 1 and 0 <= cur_y <= col - 1 and grid[cur_x][cur_y] == "1":
                            grid[cur_x][cur_y] = 0
                            queue.append([cur_x, cur_y])
        return nums


    def numIslands3(self, grid):
        """
        并查集:
        :type grid: List[List[str]]
        :rtype: int
        """
        row = len(grid)
        if row == 0:
            return 0
        col = len(grid[0])
        union = UnionFind(grid)
        for i in range(row):
            for j in range(col):
                if grid[i][j] == "1":
                    grid[i][j] == "0"
                    for cur_x, cur_y in [(i + 1, j), (i - 1, j), (i, j + 1), (i, j - 1)]:
                        if 0 <= cur_x <= row - 1 and 0 <= cur_y <= col - 1 and grid[cur_x][cur_y] == "1":
                            union.merge(i*col+j, cur_x*col+cur_y)
        return union.get_count()


class UnionFind:
    def __init__(self, grid):
        """
        并查集初始化,并查集的重要思想在于,用集合中的一个元素代表集合,可以参考树结构用根节点表示这个树,同一个集合就是一棵树
        :param grid:
        """
        row = len(grid)
        col = len(grid[0])
        # parent数组保存的是当前索引位置对应的父节点的索引值
        self.parent = [i for i in range(row*col)]
        # 标记对应索引位置所在子树/集合的复杂度,用处在于在合并两个集合/子树时,优先把复杂度低的集合合并到复杂度高的集合中,这样整体复杂度不变
        self.rank = [0] * (row*col)
        self.count = 0
        for i in range(row):
            for j in range(col):
                if grid[i][j]=="1":
                    self.count+=1

    def find(self, x):
        if self.parent[x]==x:
            return x
        else:
            # 递归设置父节点为最终的根节点,能够压缩查询路径
            self.parent[x] = self.find(self.parent[x])
            return self.parent[x]

    def merge(self, x, y):
        x_parent = self.find(x)
        y_parent = self.find(y)
        # 如果这两个节点对应的祖先节点是同一个的话,代表已经merge过了
        if x_parent==y_parent:
            return

        self.count -= 1
        x_rank = self.rank[x_parent]
        y_rank = self.rank[y_parent]

        # 下面这个逻辑就是把复杂度低的子树合并到复杂度高的子树中去,复杂度相同时,把合并后的子树复杂度加一
        if x_rank<y_rank:
            self.parent[x_parent] = y_parent
        elif x_rank>y_rank:
            self.parent[y_parent] = x_parent
        else:
            self.parent[y_parent] = x_parent
            self.rank[x_parent]+=1

    def get_count(self):
        return self.count



if __name__ == '__main__':
    grid = [
        ["1"],["1"]
    ]

    sol = Solution()
    res = sol.numIslands3(grid)
    print(res)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值