200. Number of Islands
Description
Given an m x n 2D binary grid grid which represents a map of '1’s (land) and '0’s (water), return the number of islands.
An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically. You may assume all four edges of the grid are all surrounded by water.
Algorithm
This is a classical BFS question. For each position with value “1” in this grid, we use BFS to find all the related "1"s. All the “1” will be recorded in visited and queue until there’s no more “1” connected to this group of positions. Then we move on to the next position which is not in visited and whose value is “1”. We add the 1 to the total number of islands if we find a new “1”. Return the total number of islands after we traverse all the positions in this grid.
Exceptions
if there is not grid, return 0.
In each loop, if the value is “0” or the position has been added to visited before, skip the position. The position has been added to visited before is because this position is “1” and is connected to a previous “1”. So it’s not a new island. It’s in a previous island and has been counted already. So we need to skip it.
Ths same when we find the neighbors. We skip the "0"s because they are not lands and we skip any position that is our of range - beyong the grid. We also need to skip any position that has been added to visited to avoid adding the position into the queue multiple times.
Code
def numIslands(self, grid: List[List[str]]) -> int:
'''
special case
exception
'''
if not grid:
return 0
'''
neighbors can be found in four directions
keep the position difference in delta
use this to find neighbors afterwards
'''
delta = [(0, 1), (0, -1), (-1, 0), (1, 0)]
'''
implement BFS using queue
we only count the island once
need to keep a set to store the lands we visited
'''
queue = collections.deque()
visited = set()
res = 0
'''
for each position, we need to check:
if it's a valid island: not visited and value = "1"
'''
for i in range(len(grid)):
for j in range(len(grid[0])):
if (i, j) in visited or grid[i][j] == "0":
continue
'''
if (i, j) is a valid island:
add (i, j) into queue and visited
add 1 to the result
'''
res += 1
queue.append((i, j))
visited.add((i, j))
'''
for each valid island
use bfs to find all the connected "1"s
until there's no new connected position available
'''
while queue:
current = queue.popleft()
'''
find all the valid neighbors:
1. neighbor should be in grid
2. neighbor should have value "1"
3. neighbor not in visited
'''
for neighbor in self.get_neighbor(current, grid, delta):
if neighbor in visited:
continue
'''
add all the valid neighbors into the queue
find the neighbor of newly added valid position in next round
'''
queue.append(neighbor)
visited.add(neighbor)
'''
when queue is empty, end the while loop
Now, all the valid neighbors connected to (i, j) have been found
move on to next position
'''
'''
after 2 layers of for loop
all the position in grid have been checked
return the result
'''
return res
'''
define a helper function here to keep main function clear
find all the "1"s connected to the current position
current position: the one poped from queue in this round
need to leverage delta to keep it clean
'''
def get_neighbor(self, current, grid, delta):
neighbors = []
for dx, dy in delta:
new_x = current[0] + dx
new_y = current[1] + dy
if 0 <= new_x < len(grid) and 0 <= new_y < len(grid[0]) and grid[new_x][new_y] == "1":
neighbors.append((new_x, new_y))
return neighbors
Tips
we are using (i, j) as the key of visited set. Python can do this. But we can also convert the position to a number: key = i * num_of_columns + j. This value is unique for all the position in grid. row == i == key // num_of_columns, column == j == key % num_of_columns
Complexity
O(m n) we have to visit all the position in grid and all their neighbors. There are total m x n nodes and each node has 4 neighbors. So it’s 4mn which is O(mn)