岛屿类问题的广度优先深度优先双解法(Leetcode题解-Python语言)

695. 岛屿的最大面积

先上最经典的题目,详细思路看这题的官方题解,简单来说的岛屿问题就是遍历二维数组,一般都是从一块陆地开始,进行深度优先或者广度优先搜索,每次上下左右四个方向选其一然后寻找下一块陆地,最后找到一整个岛屿(一片陆地)。

广度优先搜索:

class Solution:
    def maxAreaOfIsland(self, grid: List[List[int]]) -> int:
        ans = 0
        nr = len(grid)
        if nr == 0:
            return 0
        nc = len(grid[0])

        for r in range(nr):
            for c in range(nc):
                if grid[r][c] == 1:
                    queue = collections.deque([(r, c)]) # 队列
                    grid[r][c] = 2
                    cur = 1
                    while queue:
                        row, col = queue.popleft() # 先进先出
                        for x, y in [(row - 1, col), (row + 1, col), (row, col - 1), (row, col + 1)]:
                            if 0 <= x < nr and 0 <= y < nc and grid[x][y] == 1:
                                cur += 1
                                queue.append((x, y))
                                grid[x][y] = 2
                    ans = max(ans, cur)
        
        return ans

nr是行的长度,nc是列的长度,遍历整个二维数组,如果遇到陆地if grid[r][c] == 1就开始找岛屿。广度优先搜索用的是队列,记录的是二维坐标。grid[r][c] = 2的目的是防止重复遍历。cur是当前岛屿的面积。从一块陆地出发,如果它的四个方向[(row - 1, col), (row + 1, col), (row, col - 1), (row, col + 1)]的下一个坐标(x, y)在范围内并且为陆地的话,就把它加入队列中,同时标记已遍历,cur也加1。ans记录最大的岛屿面积。

深度优先搜索:

class Solution:
    def maxAreaOfIsland(self, grid: List[List[int]]) -> int:
        ans = 0
        nr = len(grid)
        if nr == 0:
            return 0
        nc = len(grid[0])

        for r in range(nr):
            for c in range(nc):
                if grid[r][c] == 1:
                    stack = [(r, c)] # 栈
                    grid[r][c] = 2
                    cur = 1
                    while stack:
                        row, col = stack.pop() # 先进后出
                        for x, y in [(row - 1, col), (row + 1, col), (row, col - 1), (row, col + 1)]:
                            if 0 <= x < nr and 0 <= y < nc and grid[x][y] == 1:
                                cur += 1
                                stack.append((x, y))
                                grid[x][y] = 2
                    ans = max(ans, cur)
        
        return ans

深度优先搜索和广度优先搜索基本一样,只是改用来实现,每次弹出的位置就是刚刚加入的,即一条路走到底,再往回走(深度);广度优先则按顺序弹出,一定把最初的位置遍历完之后再遍历新的,如同石头落入水中产生涟漪(广度)。

广度优先与深度优先切换,只需要改一条初始化语句,一个pop函数,几个变量名就行了。

200. 岛屿数量

广度优先搜索:

class Solution:
    def numIslands(self, grid: List[List[str]]) -> int:
        ans = 0
        nr = len(grid)
        if nr == 0:
            return 0
        nc = len(grid[0])

        for r in range(nr):
            for c in range(nc):
                if grid[r][c] == "1":
                    queue = collections.deque([(r, c)])
                    grid[r][c] = "2"
                    ans += 1
                    while queue:
                        row, col = queue.popleft()
                        for x, y in [(row - 1, col), (row + 1, col), (row, col - 1), (row, col + 1)]:
                            if 0 <= x < nr and 0 <= y < nc and grid[x][y] == "1":
                                queue.append((x, y))
                                grid[x][y] = "2"
        
        return ans

深度优先搜索:

class Solution:
    def numIslands(self, grid: List[List[str]]) -> int:
        ans = 0
        nr = len(grid)
        if nr == 0:
            return 0
        nc = len(grid[0])

        for r in range(nr):
            for c in range(nc):
                if grid[r][c] == "1":
                    stack = [(r, c)]
                    grid[r][c] = "2"
                    ans += 1
                    while stack:
                        row, col = stack.pop()
                        for x, y in [(row - 1, col), (row + 1, col), (row, col - 1), (row, col + 1)]:
                            if 0 <= x < nr and 0 <= y < nc and grid[x][y] == "1":
                                stack.append((x, y))
                                grid[x][y] = "2"
        
        return ans

代码框架基本相同,区别:这题的数字用字符来表示,是“1”而不是1;不需要记录当前岛屿的面积,只需要在进入一块新陆地找岛屿时ans加1即可,实际上比上一题简单。

694. 不同岛屿的数量

广度优先搜索:

class Solution:
    def numDistinctIslands(self, grid: List[List[int]]) -> int:
        ans = set()
        nr = len(grid)
        if nr == 0:
            return 0
        nc = len(grid[0])

        for r in range(nr):
            for c in range(nc):
                if grid[r][c] == 1:
                    queue = collections.deque([(r, c)])
                    grid[r][c] = 2
                    path = 'a'
                    while queue:
                        row, col = queue.popleft()
                        nxt = [(row - 1, col), (row + 1, col), (row, col - 1), (row, col + 1)]
                        for i in range(4): # 为了记录方向
                            x, y = nxt[i]
                            if 0 <= x < nr and 0 <= y < nc and grid[x][y] == 1:
                                path += str(i)
                                queue.append((x, y))
                                grid[x][y] = 2
                        path += 'a'
                    ans.add(path)
        
        return len(ans)

深度优先搜索:

class Solution:
    def numDistinctIslands(self, grid: List[List[int]]) -> int:
        ans = set()
        nr = len(grid)
        if nr == 0:
            return 0
        nc = len(grid[0])

        for r in range(nr):
            for c in range(nc):
                if grid[r][c] == 1:
                    stack = [(r, c)]
                    grid[r][c] = 2
                    path = 'a'
                    while stack:
                        row, col = stack.pop()
                        nxt = [(row - 1, col), (row + 1, col), (row, col - 1), (row, col + 1)]
                        for i in range(4): # 为了记录方向
                            x, y = nxt[i]
                            if 0 <= x < nr and 0 <= y < nc and grid[x][y] == 1:
                                path += str(i)
                                stack.append((x, y))
                                grid[x][y] = 2
                        path += 'a'
                    ans.add(path)
        
        return len(ans)

这题找的是不同岛屿的数量,如何定义岛屿是否相同(模式)呢?题目说明:两个岛屿被认为是相同的,当且仅当一个岛屿可以通过平移变换(不可以旋转、翻转)和另一个岛屿重合。由于我们找陆地时是从左上向右下找的,所以进入某种形状岛屿的位置是确定的,因此,可以用探索岛屿的路径(方向组合)来表示这个岛屿(类似动作游戏的上下左右组合技能)。

具体方法就是用字符串记录这个方向的组合,同时要避免不同形状的岛屿出现相同路径的情况,在每次遍历时加入标记 path += 'a',保证路径的唯一性。

463. 岛屿的周长

广度优先搜索:

class Solution:
    def islandPerimeter(self, grid: List[List[int]]) -> int:
        ans = 0
        nr = len(grid)
        if nr == 0:
            return 0
        nc = len(grid[0])

        for r in range(nr):
            for c in range(nc):
                if grid[r][c] == 1:
                    stack = [(r, c)]
                    grid[r][c] = 2
                    while stack:
                        row, col = stack.pop()
                        for x, y in [(row - 1, col), (row + 1, col), (row, col - 1), (row, col + 1)]:
                            if (x < 0 or x >= nr or y < 0 or y >= nc) or (0 <= x < nr and 0 <= y < nc and grid[x][y] == 0):
                                    ans += 1
                            if 0 <= x < nr and 0 <= y < nc and grid[x][y] == 1:
                                stack.append((x, y))
                                grid[x][y] = 2
        
        return ans

深度优先搜索:

class Solution:
    def islandPerimeter(self, grid: List[List[int]]) -> int:
        ans = 0
        nr = len(grid)
        if nr == 0:
            return 0
        nc = len(grid[0])

        for r in range(nr):
            for c in range(nc):
                if grid[r][c] == 1:
                    stack = [(r, c)]
                    grid[r][c] = 2
                    while stack:
                        row, col = stack.pop()
                        for x, y in [(row - 1, col), (row + 1, col), (row, col - 1), (row, col + 1)]:
                            if (x < 0 or x >= nr or y < 0 or y >= nc) or (0 <= x < nr and 0 <= y < nc and grid[x][y] == 0):
                                    ans += 1
                            if 0 <= x < nr and 0 <= y < nc and grid[x][y] == 1:
                                stack.append((x, y))
                                grid[x][y] = 2
        
        return ans

本题中只有一个岛屿,注意周长即岛屿的边界,只会出现在陆地与大边界或者海洋的交界处,即指向四个方向的下一个坐标(x,y)有 if (x < 0 or x >= nr or y < 0 or y >= nc) or (0 <= x < nr and 0 <= y < nc and grid[x][y] == 0):越界或者是海洋。

827. 最大人工岛

广度优先搜索:

class Solution:
    def largestIsland(self, grid: List[List[int]]) -> int:
        ans = 0
        nr = len(grid)
        if nr == 0:
            return 0
        nc = len(grid[0])

        area = {}
        index = 2

        for r in range(nr):
            for c in range(nc):
                if grid[r][c] == 1:
                    queue = collections.deque([(r, c)]) # 队列
                    grid[r][c] = index
                    cur = 1
                    while queue:
                        row, col = queue.popleft() # 先进先出
                        for x, y in [(row - 1, col), (row + 1, col), (row, col - 1), (row, col + 1)]:
                            if 0 <= x < nr and 0 <= y < nc and grid[x][y] == 1:
                                cur += 1
                                queue.append((x, y))
                                grid[x][y] = index
                    area[index] = cur
                    index += 1

        ans = max(area.values() or [0])

        for r in range(nr):
            for c in range(nc):
                if grid[r][c] == 0:
                    seen = set()
                    for x, y in [(r - 1, c), (r + 1, c), (r, c - 1), (r, c + 1)]:
                        if 0 <= x < nr and 0 <= y < nc and grid[x][y] > 1:
                            seen.add(grid[x][y])
                        ans = max(ans, 1 + sum(area[i] for i in seen))

        return ans

深度优先搜索:

class Solution:
    def largestIsland(self, grid: List[List[int]]) -> int:
        ans = 0
        nr = len(grid)
        if nr == 0:
            return 0
        nc = len(grid[0])

        area = {}
        index = 2

        for r in range(nr):
            for c in range(nc):
                if grid[r][c] == 1:
                    stack = [(r, c)] # 队列
                    grid[r][c] = index
                    cur = 1
                    while stack:
                        row, col = stack.pop() # 先进先出
                        for x, y in [(row - 1, col), (row + 1, col), (row, col - 1), (row, col + 1)]:
                            if 0 <= x < nr and 0 <= y < nc and grid[x][y] == 1:
                                cur += 1
                                stack.append((x, y))
                                grid[x][y] = index
                    area[index] = cur
                    index += 1

        ans = max(area.values() or [0]) # 防止全1的情况

        for r in range(nr):
            for c in range(nc):
                if grid[r][c] == 0:
                    seen = set()
                    for x, y in [(r - 1, c), (r + 1, c), (r, c - 1), (r, c + 1)]:
                        if 0 <= x < nr and 0 <= y < nc and grid[x][y] > 1:
                            seen.add(grid[x][y])
                        ans = max(ans, 1 + sum(area[i] for i in seen))

        return ans

一道困难题,基本思路是将现有的岛屿遍历一次,用字典标记不同的岛屿和岛屿面积,然后再遍历所有的海洋格子,填充该格子后的大岛屿面积就是这个格子四个方向的任两个岛屿面积之和加1,找出最大值即可。

1034. 边界着色

递归法:

class Solution:
    def colorBorder(self, grid: List[List[int]], row: int, col: int, color: int) -> List[List[int]]:
        m, n = len(grid), len(grid[0])
        explored = set()

        def dfs(r, c):
            # 当前是遍历过的连通分量
            if (r, c) in explored:
                return False
            # 当前越界了,说明上一次的在网格的边界上
            if r < 0 or c < 0 or r == m or c == n:
                return True
            # 当前不是连通分量,说明上一次的与不在分量中的网格相邻
            if grid[r][c] != grid[row][col]:
                return True
            # 当前的是范围内的没遍历过的连通分量
            explored.add((r,c))
            isBorder = False
            for dx, dy in (0,1),(1,0),(0,-1),(-1,0):
                if dfs(r+dx, c+dy):
                    isBorder = True
            if isBorder:
                grid[r][c] = color
            return False
        
        dfs(row, col)
        return grid

迭代法:

class Solution:
    def colorBorder(self, grid: List[List[int]], row: int, col: int, color: int) -> List[List[int]]:
        m, n = len(grid), len(grid[0])
        explored = set()
        original_color = grid[row][col]
        stack = [(row, col)]

        while stack:
            r, c = stack.pop()
            explored.add((r,c))
            for dx, dy in (0,1),(1,0),(0,-1),(-1,0):
                # 下一步越界了,说明(r,c)在网格的边界上
                if r+dx < 0 or c+dy < 0 or r+dx == m or c+dy == n:
                    grid[r][c] = color
                # 遍历过的连通分量跳过
                elif (r+dx, c+dy) in explored:
                    continue
                # 下一步不是连通分量,说明(r,c)与不在分量中的网格相邻
                elif grid[r+dx][c+dy] != original_color:
                    grid[r][c] = color
                else:
                    stack.append((r+dx, c+dy))
        
        return grid

这题指定了遍历的起点 (row, col),所以迭代法实现中就不能用 for 循环而要用 while 循环。从起点出发,记录下遍历过的节点,然后考察其四个方向的下一个节点:如果下一个的节点越界了,说明当前节点在边界上,则需要着色;如果下一个节点是遍历过的连通分量,则跳过;如果下一个节点不是连通分量,则需要着色;剩下的情况就是下一个节点是在边界内的、为遍历过的连通分量,则加入栈。

1765. 地图中的最高点

class Solution:
    def highestPeak(self, isWater: List[List[int]]) -> List[List[int]]:
        m, n = len(isWater), len(isWater[0])
        ans = [[water-1 for water in row] for row in isWater]
        q = collections.deque((i, j) for i, row in enumerate(isWater) for j, _ in enumerate(row) if ans[i][j] == 0)
        while q:
            i, j = q.popleft()
            for x, y in ((i+1, j), (i-1, j), (i, j+1), (i, j-1)):
                if 0 <= x < m and 0 <= y < n and ans[x][y] == -1:
                    ans[x][y] = ans[i][j] + 1
                    q.append((x, y))
        return ans

广度优先遍历,先把所有的 water 方格坐标加入队列作为起始点,然后遍历它们上下左右的格子,如果是还没被遍历过的,就在 ans 中高度加 1 并且加入队列,直到没有格子为止。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值