【算法-面试】深度优先遍历dfs专题

1. 深度优先遍历

  • 130 被围绕的区域
  • 200 岛屿数量
  • 694 不同的岛屿数量
  • 695 岛屿的最大积
  • 1020 飞地的数量
  • 1254 统计封闭岛屿的数
  • 1905 统计岛屿

2. 题目

【130 被围绕的区域】

class UnionFind:
    def __init__(self, n):
        self._count = n
        self.parent = [i for i in range(n)]
        self.size = [1 for _ in range(n)]

        for i in range(n):
            self.parent[i] = i
            self.size[i] = 1

    def union(self, p, q):
        root_p = self.find(p)
        root_q = self.find(q)
        if root_q == root_p:
            return
        # 小树接到大树下面,较平衡
        if self.size[root_p] > self.size[root_q]:
            self.parent[root_q] = root_p
            self.size[root_p] += self.size[root_q]
        else:
            self.parent[root_p] = root_q
            self.size[root_q] += self.size[root_p]
        self._count -= 1

    def find(self, x):
        while self.parent[x] != x:
            self.parent[x] = self.parent[self.parent[x]]
            x = self.parent[x]
        return x

    def connected(self, p, q):
        root_p = self.find(p)
        root_q = self.find(q)
        return root_p == root_q

    @property
    def count(self):
        return self._count

【130. 被围绕的区域】

def connectedArea(board):
    '''
    给你⼀个 m x n 的矩阵 board,由若⼲字符 'X' 和 'O' 组成,找到所有被 'X' 围绕的区域,并将这些区域⾥所有的 'O' ⽤ 'X' 填充。
    leetcode: 130. 被围绕的区域
    input:board = [
                    ["X","X","X","X"],
                    ["X","O","O","X"],
                    ["X","X","O","X"],
                    ["X","O","X","X"]
                  ]
    output: [
                ["X","X","X","X"],
                ["X","X","X","X"],
                ["X","X","X","X"],
                ["X","O","X","X"]
            ]
            被围绕的区间不会存在于边界上,换句话说,任何边界上的 'O' 都不会被填充为 'X'。
            任何不在边界上,或不与边界上的 'O' 相连的 'O' 最终都会被填充为 'X'。
            如果两个元素在⽔平或垂直⽅向相邻,则称它们是“相连”的
    思路:
        1.
        2.
        3.
    '''
    if len(board) == 0:
        return 0
    m, n = len(board), len(board[0])
    uf = UnionFind(m * n + 1)
    dummy = m * n
    # 首列和末列的O与dummy相连
    for i in range(m):
        if board[i][0] == "O":
            uf.union(i * n, dummy)
        if board[i][n - 1] == "O":
            uf.union(i * n + n - 1, dummy)

    # 首行和末行的O与dummy相连
    for j in range(n):
        if board[0][j] == "O":
            uf.union(j, dummy)
        if board[m - 1][j] == "O":
            uf.union(n * (m - 1) + j, dummy)

    direct = [[1, 0], [0, 1], [0, -1], [-1, 0]]
    for i in range(1, m - 1):
        for j in range(1, n - 1):
            if board[i][j] == "O":
                for k in range(0, 4):
                    x = i + direct[k][0]
                    y = j + direct[k][1]
                    if board[x][y] == "O":
                        uf.union(x * n + y, i * n + j)

    for i in range(1, m - 1):
        for j in range(1, n - 1):
            if not uf.connected(dummy, i * n + j):
                board[i][j] = 'X'

    for b in board:
        print(b)
    print('\n')

【200 岛屿数量】

def numIslands(grid):
    '''
    给你⼀个由 '1'(陆地)和 '0'(⽔)组成的的⼆维⽹格,请你计算⽹格中岛屿的数量。
    岛屿总是被⽔包围,并且每座岛屿只能由⽔平⽅向和/或竖直⽅向上相邻的陆地连接形成。此外,你可以假设该⽹格的四条边均被⽔包围。
    leetcode:200 岛屿数量
    input:grid = [
                ["1","1","1","1","0"],
                ["1","1","0","1","0"],
                ["1","1","0","0","0"],
                ["0","0","0","0","0"]
            ]
    output: 1
    思路:
        1.
        2.
        3.
    '''
    res = 0
    m, n = len(grid), len(grid[0])

    def dfs(_grid, i, j):
        m, n = len(_grid), len(_grid[0])
        if i < 0 or j < 0 or i > m or j > n:
            return
        if _grid[i][j] == '0':
            return

        _grid[i][j] = '0'
        dfs(_grid, i + 1, j)
        dfs(_grid, i, j + 1)
        dfs(_grid, i - 1, j)
        dfs(_grid, i, j - 1)

    for i in range(m):
        for j in range(n):
            if grid[i][j] == '1':
                res += 1
                dfs(grid, i, j)

    for g in grid:
        print(g)

    print(res)

【 1254 统计封闭岛屿的数目】

def closedIsland(grid):
    '''
    有一个二维矩阵 grid ,每个位置要么是陆地(记号为 0 )要么是水域(记号为 1 )。
    我们从一块陆地出发,每次可以往上下左右 4 个方向相邻区域走,能走到的所有陆地区域,我们将其称为一座「岛屿」。
    如果一座岛屿 完全 由水域包围,即陆地边缘上下左右所有相邻区域都是水域,那么我们将其称为 「封闭岛屿」。
    请返回封闭岛屿的数目。其中0是陆地,1是海水。
    leetcode: 1254 统计封闭岛屿的数目
    input:grid = [
                    [1,1,1,1,1,1,1,0],
                    [1,0,0,0,0,1,1,0],
                    [1,0,1,0,1,1,1,0],
                    [1,0,0,0,0,1,0,1],
                    [1,1,1,1,1,1,1,0]
                ]
    output:2
    思路:
        1.
        2.
        3.
    '''
    m, n = len(grid), len(grid[0])  # m行 n列

    def dfs(_grid, i, j):
        m, n = len(_grid), len(_grid[0])
        if i >= m or j >= n or i < 0 or j < 0:
            return
        if _grid[i][j] == 1:
            # 1代表海水,已经被淹没过了
            return

        _grid[i][j] = 1
        dfs(_grid, i + 1, j)
        dfs(_grid, i, j + 1)
        dfs(_grid, i - 1, j)
        dfs(_grid, i, j - 1)

    # 将首行和末行淹没
    for i in range(n):
        dfs(grid, 0, i)
        dfs(grid, m - 1, i)
    # 将首列和末列淹没
    for i in range(m):
        dfs(grid, i, 0)
        dfs(grid, i, n - 1)

    res = 0
    for i in range(m):
        for j in range(n):
            if grid[i][j] == 0:
                res += 1
                dfs(grid, i, j)
    for g in grid:
        print(g)
    print(res)
    return res

【1020 飞地的数量】


def numEnclaves(grid):
    '''
    给出一个二维数组 A,每个单元格为 0(代表海)或 1(代表陆地)。
    移动是指在陆地上从一个地方走到另一个地方(朝四个方向之一)或离开网格的边界。
    返回网格中无法在任意次数的移动中离开网格边界的陆地单元格的数量。 1代表陆地,0代表海洋
    leetcode: 1020 飞地的数量
    input: [
                [0,0,0,0],
                [1,0,1,0],
                [0,1,1,0],
                [0,0,0,0]
            ]
    output: 3
    思路:
        1.
        2.
        3.
    '''
    m, n = len(grid), len(grid[0])
    res = 0

    def dfs(_grid, i, j):
        m, n = len(grid), len(grid[0])
        if i < 0 or j < 0 or i >= m or j >= n:
            return
        if _grid[i][j] == 0:
            return
        _grid[i][j] = 0
        dfs(_grid, i + 1, j)
        dfs(_grid, i, j + 1)
        dfs(_grid, i - 1, j)
        dfs(_grid, i, j - 1)

    for i in range(m):
        dfs(grid, i, 0)
        dfs(grid, i, n - 1)

    for i in range(n):
        dfs(grid, 0, i)
        dfs(grid, m - 1, i)

    for i in range(m):
        for j in range(n):
            if grid[i][j] == 1:
                res += 1

    print(res)
    return res

【 695 岛屿的最大面积】

def maxAreaOfIsland(grid):
    '''
    给定一个包含了一些 0 和 1的非空二维数组 grid , 一个 岛屿 是由四个方向 (水平或垂直) 的 1 (代表土地) 构成的组合。
    你可以假设二维矩阵的四个边缘都被水包围着。
    找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为0。) 0表示海水,1表示陆地
    leetcode: 695 岛屿的最大面积
    input:
        [
            [0,0,1,0,0,0,0,1,0,0,0,0,0],
            [0,0,0,0,0,0,0,1,1,1,0,0,0],
            [0,1,1,0,1,0,0,0,0,0,0,0,0],
            [0,1,0,0,1,1,0,0,1,0,1,0,0],
            [0,1,0,0,1,1,0,0,1,1,1,0,0],
            [0,0,0,0,0,0,0,0,0,0,1,0,0],
            [0,0,0,0,0,0,0,1,1,1,0,0,0],
            [0,0,0,0,0,0,0,1,1,0,0,0,0]
        ]
    output: 6
    思路:
        1.
        2.
        3.
    '''
    res = 0
    m, n = len(grid), len(grid[0])

    def dfs(_grid, i, j):
        m, n = len(_grid), len(_grid[0])
        if i < 0 or j < 0 or i >= m or j >= n:
            return 0
        if _grid[i][j] == 0:
            return 0
        _grid[i][j] = 0
        # 返回陆地面积
        return dfs(_grid, i + 1, j) + \
               dfs(_grid, i, j + 1) + \
               dfs(_grid, i - 1, j) + \
               dfs(_grid, i, j - 1) + 1

    for i in range(m):
        for j in range(n):
            if grid[i][j] == 1:
                #  淹没岛屿,并更新最大岛屿面积
                res = max(res, dfs(grid, i, j))
    print(res)
    return res

【1905 统计子岛屿】

def countSubIslands(grid1, grid2):
    '''
    给你两个m x n的二进制矩阵grid1和grid2,它们只包含0(表示水域)和1(表示陆地)。一个岛屿是由四个方向(水平或者竖直)上相邻的1组成的区域。任何矩阵以外的区域都视为水域。
    如果grid2的一个岛屿,被grid1的一个岛屿完全包含,也就是说grid2中该岛屿的每一个格子都被grid1中同一个岛屿完全包含,那么我们称grid2中的这个岛屿为子岛屿。
    请你返回grid2中子岛屿的数目。0表示海水,1表示陆地
    leetcode: 1905 统计子岛屿
    input:  grid1 = [
                        [1,1,1,0,0],
                        [0,1,1,1,1],
                        [0,0,0,0,0],
                        [1,0,0,0,0],
                        [1,1,0,1,1]
                    ],
            grid2 = [
                      [1,1,1,0,0],
                      [0,0,1,1,1],
                      [0,1,0,0,0],
                      [1,0,1,1,0],
                      [0,1,0,1,0]
                    ]
    output: 3
    思路:
        1. 如果岛屿B中存在一片陆地,在岛屿A的对应位置是海水,那么岛屿B就不是岛屿A的子岛。
        2. 遍历grid2中的所有岛屿,把那些不可能是子岛的岛屿排除掉,剩下的就是子岛
        3.
    '''
    m, n = len(grid1), len(grid1[0])
    res = 0

    def dfs(_grid, i, j):
        m, n = len(_grid), len(_grid[0])
        if i < 0 or j < 0 or i >= m or j >= n:
            return
        if _grid[i][j] == 0:
            return

        _grid[i][j] = 0
        dfs(_grid, i + 1, j)
        dfs(_grid, i, j + 1)
        dfs(_grid, i - 1, j)
        dfs(_grid, i, j - 1)

    for i in range(m):
        for j in range(n):
            if grid1[i][j] == 0 and grid2[i][j] == 1:
                # grid1中是海水,grid2中是陆地的部分,淹没
                dfs(grid2, i, j)

    for i in range(m):
        for j in range(n):
            if grid2[i][j] == 1:
                # grid2中是海水的部分
                res += 1
                dfs(grid2, i, j)

    print(res)
    return res

【694 不同的岛屿数量】

def numDistinctIslands(grid):
    '''
    给定一个非空0 1二维数组表示的网格,一个岛屿由四连通(上、下、左、右四个方向)的 1 组成,你可以认为网格的四周被海水包围。
    请你计算这个网格中共有多少个形状不同的岛屿。两个岛屿被认为是相同的,当且仅当一个岛屿可以通过平移变换(不可以旋转、翻转)和另一个岛屿重合。
    leetcode: 694  不同的岛屿数量
    input:
    output:
    思路:
        1.
        2.
        3.
    '''
    m, n = len(grid), len(grid[0])
    islands = set()

    def dfs(_grid, i, j, _s, _dir):
        m, n = len(_grid), len(_grid[0])
        if i < 0 or j < 0 or i >= m or j >= n or _grid[i][j] == 0:
            return
        _grid[i][j] = 0
        _s.append(_dir)
        dfs(_grid, i - 1, j, _s, 1)
        dfs(_grid, i + 1, j, _s, 2)
        dfs(_grid, i, j - 1, _s, 3)
        dfs(_grid, i, j + 1, _s, 4)
        _s.append(-_dir)

    for i in range(m):
        for j in range(n):
            if grid[i][j] == 1:
                s = []
                dfs(grid, i, j, s, 11)
                # print(s)
                news = ",".join([str(_) for _ in s])
                print(news)
                islands.add(news)
    print(len(islands))
    return len(islands)

【测试例】

if __name__ == "__main__":
    # connectedArea([
    #     ["X", "X", "X", "X"],
    #     ["X", "O", "O", "O"],
    #     ["X", "X", "O", "X"],
    #     ["X", "O", "X", "X"]
    # ])

    # connectedArea([
    #     ["X", "X", "X", "X", "X"],
    #     ["O", "O", "O", "X", "X"],
    #     ["X", "X", "X", "O", "X"],
    #     ["X", "O", "X", "X", "X"]
    # ])

    # numIslands(
    #     [
    #         ["1", "1", "1", "1", "0"],
    #         ["1", "1", "0", "1", "0"],
    #         ["1", "1", "0", "0", "0"],
    #         ["0", "0", "0", "0", "0"]
    #     ]
    # )

    # closedIsland(
    #     [
    #         [1, 1, 1, 1, 1, 1, 1, 0],
    #         [1, 0, 0, 0, 0, 1, 1, 0],
    #         [1, 0, 1, 0, 1, 1, 1, 0],
    #         [1, 0, 0, 0, 0, 1, 0, 1],
    #         [1, 1, 1, 1, 1, 1, 1, 0]
    #     ]
    # )

    # numEnclaves(
    #     [
    #         [0, 0, 0, 0],
    #         [1, 0, 1, 0],
    #         [0, 1, 1, 0],
    #         [0, 0, 0, 0]
    #     ]
    # )

    # maxAreaOfIsland(
    #     [
    #         [0, 0, 0, 0],
    #         [1, 0, 1, 0],
    #         [0, 1, 1, 0],
    #         [0, 0, 0, 0]
    #     ]
    # )
    # countSubIslands([
    #     [1, 1, 1, 0, 0],
    #     [0, 1, 1, 1, 1],
    #     [0, 0, 0, 0, 0],
    #     [1, 0, 0, 0, 0],
    #     [1, 1, 0, 1, 1]
    # ], [
    #     [1, 1, 1, 0, 0],
    #     [0, 0, 1, 1, 1],
    #     [0, 1, 0, 0, 0],
    #     [1, 0, 1, 1, 0],
    #     [0, 1, 0, 1, 0]
    # ]
    # )
    numDistinctIslands(
        [
            [1, 1, 0, 1, 1],
            [1, 0, 0, 0, 0],
            [0, 0, 0, 0, 0],
            [0, 0, 0, 0, 1],
            [1, 1, 0, 1, 1]
        ]
    )

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值