Two islands are considered the same if one can be translated (shifted) to match the other. To identify distinct islands, we need a way to represent each island's shape independent of its position. We do this by normalizing each island: recording each cell's position relative to the island's origin (the first cell we encounter). Then we compare each new island against all previously found unique islands.
dfs to explore each island, recording cells as offsets from the starting cell (origin).class Solution:
def numDistinctIslands(self, grid: List[List[int]]) -> int:
def current_island_is_unique():
for other_island in unique_islands:
if len(other_island) != len(current_island):
continue
for cell_1, cell_2 in zip(current_island, other_island):
if cell_1 != cell_2:
break
else:
return False
return True
# Do a DFS to find all cells in the current island.
def dfs(row, col):
if row < 0 or col < 0 or row >= len(grid) or col >= len(grid[0]):
return
if (row, col) in seen or not grid[row][col]:
return
seen.add((row, col))
current_island.append((row - row_origin, col - col_origin))
dfs(row + 1, col)
dfs(row - 1, col)
dfs(row, col + 1)
dfs(row, col - 1)
# Repeatedly start DFS's as long as there are islands remaining.
seen = set()
unique_islands = []
for row in range(len(grid)):
for col in range(len(grid[0])):
current_island = []
row_origin = row
col_origin = col
dfs(row, col)
if not current_island or not current_island_is_unique():
continue
unique_islands.append(current_island)
print(unique_islands)
return len(unique_islands)Where is the number of rows, and is the number of columns
Instead of comparing islands one by one, we can use a hash set for O(1) lookup. Each island is represented as a set of relative coordinates (offsets from the origin cell). Since sets of tuples are hashable (using frozenset in Python), we can directly add each island's shape to a set of unique islands. Duplicate shapes will naturally be filtered out.
dfs to explore each island, storing cells as relative coordinates from the starting cell.class Solution:
def numDistinctIslands(self, grid: List[List[int]]) -> int:
# Do a DFS to find all cells in the current island.
def dfs(row, col):
if row < 0 or col < 0 or row >= len(grid) or col >= len(grid[0]):
return
if (row, col) in seen or not grid[row][col]:
return
seen.add((row, col))
current_island.add((row - row_origin, col - col_origin))
dfs(row + 1, col)
dfs(row - 1, col)
dfs(row, col + 1)
dfs(row, col - 1)
# Repeatedly start DFS's as long as there are islands remaining.
seen = set()
unique_islands = set()
for row in range(len(grid)):
for col in range(len(grid[0])):
current_island = set()
row_origin = row
col_origin = col
dfs(row, col)
if current_island:
unique_islands.add(frozenset(current_island))
return len(unique_islands)Where is the number of rows, and is the number of columns
Another way to uniquely identify an island's shape is through its dfs traversal path. If we always explore directions in the same order (e.g., Down, Up, Right, Left), two identical shapes will produce the same sequence of moves. We record each direction taken during dfs, and importantly, we also record when we backtrack. This backtrack marker is crucial because without it, different shapes could produce the same direction sequence.
dfs to explore each island, recording the direction of each move (D, U, R, L).class Solution:
def numDistinctIslands(self, grid: List[List[int]]) -> int:
# Do a DFS to find all cells in the current island.
def dfs(row, col, direction):
if row < 0 or col < 0 or row >= len(grid) or col >= len(grid[0]):
return
if (row, col) in seen or not grid[row][col]:
return
seen.add((row, col))
path_signature.append(direction)
dfs(row + 1, col, "D")
dfs(row - 1, col, "U")
dfs(row, col + 1, "R")
dfs(row, col - 1, "L")
path_signature.append("0")
# Repeatedly start DFS's as long as there are islands remaining.
seen = set()
unique_islands = set()
for row in range(len(grid)):
for col in range(len(grid[0])):
path_signature = []
dfs(row, col, "0")
if path_signature:
unique_islands.add(tuple(path_signature))
return len(unique_islands)Where is the number of rows, and is the number of columns