Number of Islands BFS and DFS in DSA Go - Time & Space Complexity
We want to understand how the time needed to find islands grows as the grid size grows.
How does the number of steps change when the grid gets bigger?
Analyze the time complexity of the following code snippet.
func numIslands(grid [][]byte) int {
rows := len(grid)
if rows == 0 {
return 0
}
cols := len(grid[0])
count := 0
for r := 0; r < rows; r++ {
for c := 0; c < cols; c++ {
if grid[r][c] == '1' {
bfs(grid, r, c)
count++
}
}
}
return count
}
func bfs(grid [][]byte, r, c int) {
queue := [][]int{{r, c}}
grid[r][c] = '0'
directions := [][]int{{1,0}, {-1,0}, {0,1}, {0,-1}}
for len(queue) > 0 {
curr := queue[0]
queue = queue[1:]
for _, d := range directions {
nr, nc := curr[0]+d[0], curr[1]+d[1]
if nr >= 0 && nr < len(grid) && nc >= 0 && nc < len(grid[0]) && grid[nr][nc] == '1' {
queue = append(queue, []int{nr, nc})
grid[nr][nc] = '0'
}
}
}
}
This code counts islands in a grid by scanning each cell and using BFS to mark connected land.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Visiting each cell once during the grid scan and BFS traversal.
- How many times: Each cell is checked once in the nested loops, and each land cell is visited once in BFS.
As the grid size grows, the number of cells to check grows with rows x columns.
| Input Size (n = rows x cols) | Approx. Operations |
|---|---|
| 10 | About 10 checks and visits |
| 100 | About 100 checks and visits |
| 1000 | About 1000 checks and visits |
Pattern observation: The steps grow roughly in direct proportion to the total number of cells.
Time Complexity: O(rows x cols)
This means the time needed grows linearly with the total number of cells in the grid.
[X] Wrong: "The BFS inside the loop makes this O((rows x cols)²) because it repeats for each cell."
[OK] Correct: Each cell is visited only once overall, so BFS does not repeat work on the same cells multiple times.
Understanding this helps you explain how to efficiently explore connected parts in grids, a common real-world problem.
"What if we used DFS recursion instead of BFS? How would the time complexity change?"
Practice
Solution
Step 1: Understand the grid content
The grid contains '1's representing land and '0's representing water.Step 2: Define the problem goal
We need to count how many separate groups of connected '1's (islands) exist.Final Answer:
Count how many groups of connected '1's (islands) are in the grid -> Option AQuick Check:
Number of islands = count of connected '1' groups [OK]
- Counting individual '1's instead of groups
- Confusing '0's as islands
- Trying to find island size instead of count
Solution
Step 1: Check boundary and water conditions
func dfs(grid [][]byte, i, j int) { if i < 0 || j < 0 || i >= len(grid) || j >= len(grid[0]) || grid[i][j] == '0' { return } grid[i][j] = '0'; dfs(grid, i+1, j); dfs(grid, i-1, j); dfs(grid, i, j+1); dfs(grid, i, j-1) } correctly checks if indices are out of bounds or cell is water ('0'), then returns.Step 2: Mark current land as visited and recurse
func dfs(grid [][]byte, i, j int) { if i < 0 || j < 0 || i >= len(grid) || j >= len(grid[0]) || grid[i][j] == '0' { return } grid[i][j] = '0'; dfs(grid, i+1, j); dfs(grid, i-1, j); dfs(grid, i, j+1); dfs(grid, i, j-1) } marks current cell as '0' to avoid revisiting and calls dfs on neighbors up, down, left, right.Final Answer:
func dfs(grid [][]byte, i, j int) { if i < 0 || j < 0 || i >= len(grid) || j >= len(grid[0]) || grid[i][j] == '0' { return } grid[i][j] = '0'; dfs(grid, i+1, j); dfs(grid, i-1, j); dfs(grid, i, j+1); dfs(grid, i, j-1) } -> Option CQuick Check:
DFS marks visited land and explores neighbors [OK]
- Not checking boundaries before access
- Not marking visited cells
- Using wrong loop instead of recursion
grid := [][]byte{
{'1','1','0','0','0'},
{'1','1','0','0','0'},
{'0','0','1','0','0'},
{'0','0','0','1','1'},
}
count := numIslands(grid)
fmt.Println(count)
func numIslands(grid [][]byte) int {
rows, cols := len(grid), len(grid[0])
count := 0
directions := [][]int{{1,0},{-1,0},{0,1},{0,-1}}
for i := 0; i < rows; i++ {
for j := 0; j < cols; j++ {
if grid[i][j] == '1' {
count++
queue := [][]int{{i,j}}
grid[i][j] = '0'
for len(queue) > 0 {
r, c := queue[0][0], queue[0][1]
queue = queue[1:]
for _, d := range directions {
nr, nc := r+d[0], c+d[1]
if nr >= 0 && nr < rows && nc >= 0 && nc < cols && grid[nr][nc] == '1' {
queue = append(queue, []int{nr,nc})
grid[nr][nc] = '0'
}
}
}
}
}
}
return count
}Solution
Step 1: Identify islands in the grid
There are three groups of connected '1's: top-left block, middle single '1', bottom-right block.Step 2: BFS marks visited and counts islands
Each BFS call marks one island, so count increments 3 times.Final Answer:
3 -> Option BQuick Check:
Count islands = 3 [OK]
- Counting connected '1's individually
- Not marking visited cells
- Miscounting overlapping islands
func dfs(grid [][]byte, i, j int) {
if i < 0 || j < 0 || i > len(grid) || j > len(grid[0]) || grid[i][j] == '0' {
return
}
grid[i][j] = '0'
dfs(grid, i+1, j)
dfs(grid, i-1, j)
dfs(grid, i, j+1)
dfs(grid, i, j-1)
}Solution
Step 1: Analyze boundary conditions
The code uses 'i > len(grid)' and 'j > len(grid[0])' which allows i or j to equal length, causing index out of range.Step 2: Correct boundary check
It should use 'i >= len(grid)' and 'j >= len(grid[0])' to prevent invalid access.Final Answer:
Boundary checks use '>' instead of '>=' causing out-of-range errors -> Option AQuick Check:
Use '>=' for boundary checks to avoid errors [OK]
- Using '>' instead of '>=' in boundary checks
- Forgetting to mark visited cells
- Mixing BFS and DFS logic
Solution
Step 1: Understand connection rules
Diagonal connections mean neighbors include 8 directions, not just 4 (up, down, left, right).Step 2: Modify neighbor directions
Add diagonal directions: (-1,-1), (-1,1), (1,-1), (1,1) to the directions list used in BFS/DFS.Final Answer:
Add diagonal directions (top-left, top-right, bottom-left, bottom-right) to the neighbors list -> Option DQuick Check:
Include diagonals in neighbors for diagonal connectivity [OK]
- Ignoring diagonal neighbors
- Not updating directions list
- Counting without marking visited
Ready to prove it?
Pass the test and this pattern moves unknown
