The challenge is to find a translation-invariant hash function for islands.
We can consider each island having a bounding box around it and then we can translate that bounding box to the top-left of the grid. In this translated bounding box, if for two islands, the sequence of 1-cells are identical, we consider them as duplicate islands.

We visit each 1-cell at most twice, once during explore and once during id generation.
Time: , space:
.
from itertools import product as cartesian
class Solution:
def island_id(self, island, box):
min_row, max_row, min_col, max_col = box
row_offset, col_offset = min_row, min_col
nrow = max_row - min_row + 1
ncol = max_col - min_col + 1
id = []
for r, c in cartesian( range(nrow), range(ncol) ):
if (r+row_offset, c+col_offset) in island:
id.append(f"{r},{c}")
return ",".join(id)
def explore_island(self, row, col, grid, island, box):
island.append((row, col))
box[0] = min(box[0], row)
box[1] = max(box[1], row)
box[2] = min(box[2], col)
box[3] = max(box[3], col)
grid[row][col] = -1
for dr, dc in [(-1, 0), (0, 1), (1, 0), (0, -1)]:
row2, col2 = row+dr, col+dc
is_valid_cell = (0 <= row2 < len(grid)) and (0 <= col2 < len(grid[0]))
if not is_valid_cell or grid[row2][col2] != 1:
continue
self.explore_island(row2, col2, grid, island, box)
def numDistinctIslands(self, grid: List[List[int]]) -> int:
unique_islands = set()
m, n = len(grid), len(grid[0])
for row, col in cartesian( range(m), range(n) ):
if grid[row][col] != 1:
continue
island = []
box = [float("inf"), float("-inf"), float("inf"), float("-inf")]
self.explore_island(row, col, grid, island, box)
unique_islands.add( self.island_id(island, box) )
return len(unique_islands)
Note, we scan the grid top-down, left-right. As a result, for identically shaped islands, we start exploring from the same corner. In other words, if we translated these islands to the top-left of the grid, we would start exploring from the same cell.

So, after the starting cell, the sequence of explored directions would be a good hash function. We just need to mark where we backtracked, like the bottom two examples.

from itertools import product as cartesian
class Solution:
def explore_island(self, row, col, grid, island_id):
grid[row][col] = -1
for dr, dc in [(-1, 0), (0, 1), (1, 0), (0, -1)]:
row2, col2 = row+dr, col+dc
is_valid_cell = (0 <= row2 < len(grid)) and (0 <= col2 < len(grid[0]))
if not is_valid_cell or grid[row2][col2] != 1:
continue
island_id.append((dr, dc))
self.explore_island(row2, col2, grid, island_id)
# Mark backtrack
island_id.append((0, 0))
def numDistinctIslands(self, grid: List[List[int]]) -> int:
unique_islands = set()
m, n = len(grid), len(grid[0])
for row, col in cartesian( range(m), range(n) ):
if grid[row][col] != 1:
continue
island_id = []
self.explore_island(row, col, grid, island_id)
unique_islands.add( tuple(island_id) )
return len(unique_islands)
Leave a comment