【LeetCode 深度优先搜索专项】不同岛屿的数量 II(711)

1. 题目

给定一个 m × n m \times n m×n 的二维矩阵 grid ,使用或上、或下、或左、或右方向上相接的一组 1 1 1 代表一个岛屿。可以假定矩阵以外的区域都是海水。

如果两个岛屿之间形状相同,或者旋转(旋转角度仅可以为 90 90 90 180 180 180 270 270 270 度)之后形状相同,或者镜像(可以沿上下或左右镜像)之后形状相同,就称这两个岛屿是相同的。

要求返回给定 grid 中包含的不同岛屿数量。

1.1 示例

  • 示例 1 1 1

    • 输入: grid = [[1, 1, 0, 0, 0], [1, 0, 0, 0, 0], [0, 0, 0, 0, 1], [0, 0, 0, 1, 1]]
    • 输出: 1 1 1
    • 解释:如下图所示,将左上角的岛屿顺时针旋转 180 180 180 度之后将得到和右下角完全相同的岛屿。

在这里插入图片描述

  • 示例 2 2 2

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

在这里插入图片描述

1.2 说明

1.3 提示

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 50
  • grid[i][j] 仅可为 0 0 0 或者 1 1 1

1.4 进阶

你可以使用多种方法求解该问题么?

2. 解法一(深度优先搜索)

2.1 分析

作者:LeetCode

扫描二维码关注公众号,回复: 13288668 查看本文章

这道题是【LeetCode 深度优先搜索专项】不同岛屿的数量(694)的加强版,不同的是这道题规定两个岛屿仅通过平移,旋转和翻转能够重合,它们的形状就相同。

解决本题的大致思路为:首先找出每个岛屿,随后将它进行旋转和翻转操作得到各种不同的情况,接着对这些情况分别进行归一化处理,然后对归一化结果进行哈希得到具有唯一性的签名,最终所有岛屿经所有变换并归一化且哈希后得到的不同签名数量就是满足要求的不同岛屿数量。

作者: jason-2

首先,为了模拟岛屿的旋转和翻转变换,可以先将岛屿的每块陆地坐标 ( x ,   y ) (x,\textit{ }y) (x, y) 都看作向量,然后利用解析几何的知识先计算出每个向量变换后的结果,最终所有通过相同变换后的向量就自然地组成了对应岛屿经相应变换后得到的结果。具体地:

  • 由于向量 ( x ,   y ) (x,\textit{ }y) (x, y) 和其旋转 90 90 90 度和 270 270 270 度的得到的向量相互正交,因此二者内积为 0 0 0 ,所以:
    • 向量 ( x ,   y ) (x,\textit{ }y) (x, y) 旋转 90 90 90 度和 270 270 270 度的向量分别为 ( y ,   − x ) (y,\textit{ }-x) (y, x) ( − y ,   x ) (-y,\textit{ }x) (y, x)
  • 向量 ( x ,   y ) (x,\textit{ }y) (x, y) 旋转 180 180 180 度: 取反向量 ( − x ,   − y ) (-x,\textit{ }-y) (x, y) 即可;
  • 向量 ( x ,   y ) (x,\textit{ }y) (x, y) 左右翻转: x x x 取反即可,即得到 ( − x ,   y ) (-x,\textit{ }y) (x, y)
  • 向量 ( x ,   y ) (x,\textit{ }y) (x, y) 上下翻转: y y y 取反即可,即得到 ( x ,   − y ) (x,\textit{ }-y) (x, y)

实际上,在后续代码提交时会发现,上面 6 6 6 种变换(向量发生平移其坐标不变)并未涵盖官方判题时考量的所有情况1,实际还需要考虑两种情况:

  • 旋转 90 90 90 度且左右翻转的结果 ( y ,   x ) (y,\textit{ }x) (y, x) (等价于旋转 270 270 270 度且上下翻转);
  • 旋转 270 270 270 度且左右翻转的结果 ( − y ,   − x ) (-y,\textit{ }-x) (y, x) (等价于旋转 90 90 90 度且上下翻转)。

在这里插入图片描述

然后,对于每一种变换操作后得到的情况,需要计算出岛屿上每个点的归一化坐标值。具体归一化的方法是:对于坐标的每一维,先求出岛屿上所有点中这一维的最小值,再把所有点的这一维减去这个最小值。

接着,对于每一个归一化后得到的变换情况,将其中的所有坐标值放入列表中并进行排序,这样两个经过变换后能重合的岛屿就可以得到相同元素序列的列表,最后对列表进行序列化就可以比较两个变换是否对应相同形状的岛屿,此时就继续对岛屿完成了哈希。

2.2 解答

from typing import List, Tuple
from json import dumps


class Solution:
    def _dfs_flood(self, grid: List[List[int]], island: List[Tuple[int, int]], i: int, j: int):
        m, n = len(grid), len(grid[0])
        if i < 0 or i >= m or j < 0 or j >= n:
            return
        if grid[i][j] == 0:
            return
        island.append((i, j))
        grid[i][j] = 0
        self._dfs_flood(grid, island, i - 1, j)
        self._dfs_flood(grid, island, i + 1, j)
        self._dfs_flood(grid, island, i, j - 1)
        self._dfs_flood(grid, island, i, j + 1)

    def _rotoate_90_deg(self, island: List[Tuple[int, int]]) -> List[Tuple[int, int]]:
        transformed_island = []
        for abscissa, ordinate in island:
            transformed_island.append((ordinate, -abscissa))  # (y, -x)
        return transformed_island

    def _rotoate_180_deg(self, island: List[Tuple[int, int]]) -> List[Tuple[int, int]]:
        transformed_island = []
        for abscissa, ordinate in island:
            transformed_island.append((-abscissa, -ordinate))  # (-x, -y)
        return transformed_island

    def _rotoate_270_deg(self, island: List[Tuple[int, int]]) -> List[Tuple[int, int]]:
        transformed_island = []
        for abscissa, ordinate in island:
            transformed_island.append((-ordinate, abscissa))  # (-y, x)
        return transformed_island

    def _reflect_up_down(self, island: List[Tuple[int, int]]) -> List[Tuple[int, int]]:
        transformed_island = []
        for abscissa, ordinate in island:
            transformed_island.append((abscissa, -ordinate))  # (x, -y)
        return transformed_island

    def _reflect_left_right(self, island: List[Tuple[int, int]]) -> List[Tuple[int, int]]:
        transformed_island = []
        for abscissa, ordinate in island:
            transformed_island.append((-abscissa, ordinate))  # (-x, y)
        return transformed_island

    def _rotoate_90_deg_reflect_left_right(self, island: List[Tuple[int, int]]) -> List[Tuple[int, int]]:
        transformed_island = []
        for abscissa, ordinate in island:
            transformed_island.append((ordinate, abscissa))  # (y, x)
        return transformed_island

    def _rotoate_270_deg_reflect_left_right(self, island: List[Tuple[int, int]]) -> List[Tuple[int, int]]:
        transformed_island = []
        for abscissa, ordinate in island:
            transformed_island.append((-ordinate, -abscissa))  # (-y, -x)
        return transformed_island

    def _normalize(self, island: List[Tuple[int, int]]) -> List[Tuple[int, int]]:
        normalized_island = []
        smallest_abscissa = min([abscissa for abscissa, ordinate in island])
        smallest_ordinate = min([ordinate for abscissa, ordinate in island])
        for abscissa, ordinate in island:
            normalized_island.append((abscissa - smallest_abscissa,
                                      ordinate - smallest_ordinate))
        return normalized_island

    def num_distinct_islands(self, grid: List[List[int]]) -> int:
        m, n, num = len(grid), len(grid[0]), 0
        distinct_transformations = set()
        for i in range(m):
            for j in range(n):
                if grid[i][j] == 1:
                    island = []
                    self._dfs_flood(grid, island, i, j)
                    size_before = len(distinct_transformations)
                    distinct_transformations.add(
                        dumps(sorted(self._normalize(island))))
                    distinct_transformations.add(
                        dumps(sorted(self._normalize(self._rotoate_90_deg(island)))))
                    distinct_transformations.add(
                        dumps(sorted(self._normalize(self._rotoate_180_deg(island)))))
                    distinct_transformations.add(
                        dumps(sorted(self._normalize(self._rotoate_270_deg(island)))))
                    distinct_transformations.add(
                        dumps(sorted(self._normalize(self._reflect_up_down(island)))))
                    distinct_transformations.add(
                        dumps(sorted(self._normalize(self._reflect_left_right(island)))))
                    distinct_transformations.add(
                        dumps(sorted(self._normalize(self._rotoate_90_deg_reflect_left_right(island)))))
                    distinct_transformations.add(
                        dumps(sorted(self._normalize(self._rotoate_270_deg_reflect_left_right(island)))))
                    size_after = len(distinct_transformations)
                    if size_after > size_before:
                        num += 1
        return num


def main():
    grid = [[1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0],
            [1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1],
            [1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0],
            [1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0],
            [0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1],
            [0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1],
            [1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
            [0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1],
            [1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0],
            [1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1]]
    sln = Solution()
    print(sln.num_distinct_islands(grid))  # 9


if __name__ == '__main__':
    main()

2.3 复杂度

太复杂,不好分析。

另外,上述代码在计算各种变换时的代码重复度较高,还可按照下列方式进行简化:

from typing import List, Tuple
from json import dumps


class Solution:
    def _dfs_flood(self, grid: List[List[int]], island: List[Tuple[int, int]], i: int, j: int):
        m, n = len(grid), len(grid[0])
        if i < 0 or i >= m or j < 0 or j >= n:
            return
        if grid[i][j] == 0:
            return
        island.append((i, j))
        grid[i][j] = 0
        self._dfs_flood(grid, island, i - 1, j)
        self._dfs_flood(grid, island, i + 1, j)
        self._dfs_flood(grid, island, i, j - 1)
        self._dfs_flood(grid, island, i, j + 1)

    def _transform(self, island: List[Tuple[int, int]], transformation: List[Tuple[int, int]]) -> List[Tuple[int, int]]:
        transformed_island = []
        for abscissa, ordinate in island:
            transformed_island.append((transformation[0][0] * abscissa + transformation[1][0] * ordinate,
                                       transformation[0][1] * abscissa + transformation[1][1] * ordinate))
        return transformed_island

    def _normalize(self, island: List[Tuple[int, int]]) -> List[Tuple[int, int]]:
        normalized_island = []
        smallest_abscissa = min([abscissa for abscissa, ordinate in island])
        smallest_ordinate = min([ordinate for abscissa, ordinate in island])
        for abscissa, ordinate in island:
            normalized_island.append((abscissa - smallest_abscissa,
                                      ordinate - smallest_ordinate))
        return normalized_island

    def succinct_num_distinct_islands(self, grid: List[List[int]]) -> int:
        m, n, num = len(grid), len(grid[0]), 0
        transformations = [[(1, 0), (0, 1)], [(0, -1), (1, 0)], [(-1, 0), (0, -1)], [(0, 1), (-1, 0)],
                           [(1, 0), (0, -1)], [(-1, 0), (0, 1)], [(0, 1), (1, 0)], [(0, -1), (-1, 0)]]
        distinct_transformations = set()
        for i in range(m):
            for j in range(n):
                if grid[i][j] == 1:
                    island = []
                    self._dfs_flood(grid, island, i, j)
                    size_before = len(distinct_transformations)
                    for transformation in transformations:
                        distinct_transformations.add(
                            dumps(sorted(self._normalize(self._transform(island, transformation)))))
                    size_after = len(distinct_transformations)
                    if size_after > size_before:
                        num += 1
        return num


def main():
    grid = [[1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 0],
            [1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1],
            [1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0],
            [1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0],
            [0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1],
            [0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1],
            [1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1],
            [0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1],
            [1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0],
            [1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1]]
    sln = Solution()
    print(sln.succinct_num_distinct_islands(grid))  # 9


if __name__ == '__main__':
    main()


  1. 实际上,想要便捷地记忆这 8 8 8 种情况,可以这样辅助记忆,即给定一个向量坐标 ( x ,   y ) (x,\textit{ }y) (x, y) ,上述 8 8 8 种变换方式确定方法为:先确定横坐标,有 4 4 4 种可能,即分别为 x ,   − x ,   y ,   − y x,\textit{ }-x,\textit{ }y,\textit{ }-y x, x, y, y ,在确定横坐标时纵坐标除了正负号两种可能之外就已经确定了,所以一共 8 8 8 种可能。 ↩︎

猜你喜欢

转载自blog.csdn.net/weixin_37780776/article/details/121013111