title: Number of Islands slug: number-of-islands difficulty: medium leetcode_id: 200 leetcode_url: https://leetcode.com/problems/number-of-islands/ categories: - graphs - arrays patterns: - dfs - bfs - matrix-traversal description: | Given an `m × 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 surrounded by water. constraints: | - `m == grid.length` - `n == grid[i].length` - `1 <= m, n <= 300` - `grid[i][j]` is `'0'` or `'1'` examples: - input: | grid = [ ["1","1","1","1","0"], ["1","1","0","1","0"], ["1","1","0","0","0"], ["0","0","0","0","0"] ] output: "1" explanation: "All land cells are connected horizontally/vertically, forming one island." - input: | grid = [ ["1","1","0","0","0"], ["1","1","0","0","0"], ["0","0","1","0","0"], ["0","0","0","1","1"] ] output: "3" explanation: "Three separate groups of connected land cells — three islands." explanation: intuition: | Imagine looking at a map from above. Each `'1'` is a piece of land, and you want to count how many distinct landmasses (islands) exist. Two pieces of land belong to the same island if you can walk from one to the other without crossing water (moving only up, down, left, or right — not diagonally). Think of it like this: when you step onto a piece of land, you want to "explore" the entire island by visiting all connected land cells. Once you've seen the whole island, you mark it as "visited" so you don't count it again. Then you continue scanning the map for the next unvisited piece of land. This is the classic **connected components** problem on a grid. Each island is one connected component of `'1'`s. We count components by: 1. Finding an unvisited land cell (new island found!) 2. Exploring all connected land cells (mark the whole island as visited) 3. Repeat until every cell has been processed approach: | We solve this using **DFS to Explore and Mark Islands**: **Step 1: Iterate through every cell** - Scan the grid row by row, column by column - We're looking for unvisited `'1'`s — each one represents a new island   **Step 2: When land is found, count it and explore** - Increment the island count - Use DFS (or BFS) to visit all connected land cells - Mark each visited cell by changing `'1'` to `'0'` (this "sinks" the island to avoid recounting)   **Step 3: DFS exploration** - From the current cell, recursively explore all four directions (up, down, left, right) - Stop when: out of bounds, or cell is water (`'0'`) - Mark the cell as visited **before** recursive calls to prevent infinite loops   **Step 4: Return the count** - After processing all cells, return the island count   This works because once we've explored an island, all its cells are marked as `'0'`, so we'll never trigger a new exploration from those cells again. common_pitfalls: - title: Not Marking Cells as Visited description: | Without marking visited cells, two things go wrong: 1. You'll count the same island multiple times (each cell triggers a new count) 2. DFS/BFS will revisit cells infinitely, causing a stack overflow or infinite loop The cleanest solution is to modify the grid itself — change `'1'` to `'0'` when visited. Alternatively, use a separate `visited` set, but this uses extra space. wrong_approach: "Not modifying grid or using visited set" correct_approach: "grid[r][c] = '0' immediately when visiting" - title: Including Diagonal Connections description: | The problem states islands connect **horizontally or vertically** only. Diagonal cells are NOT considered adjacent. Check only 4 directions: `(r+1,c), (r-1,c), (r,c+1), (r,c-1)`. Don't include the 4 diagonal directions. wrong_approach: "Exploring 8 directions including diagonals" correct_approach: "Explore only 4 orthogonal directions" - title: Boundary Check Errors description: | Before accessing `grid[r][c]`, always verify that `r` and `c` are within bounds: - `0 <= r < rows` - `0 <= c < cols` Missing these checks causes index-out-of-bounds errors. wrong_approach: "Accessing grid[r][c] without bounds check" correct_approach: "Check bounds first: if r < 0 or r >= rows or c < 0 or c >= cols: return" key_takeaways: - "**Grid = implicit graph**: Each cell is a node; adjacent cells are connected by edges" - "**DFS/BFS for connected components**: Classic technique for counting or exploring connected regions" - "**In-place marking**: Modifying input to track visited state saves space (when allowed)" - "**Foundation for many grid problems**: Flood fill, maze solving, region counting all use this pattern" time_complexity: "O(m × n). Each cell is visited at most once by the main loop and at most once by DFS/BFS." space_complexity: "O(m × n). In the worst case (all land), the DFS recursion stack or BFS queue can hold all cells." solutions: - approach_name: DFS is_optimal: true code: | def num_islands(grid: list[list[str]]) -> int: if not grid: return 0 rows, cols = len(grid), len(grid[0]) islands = 0 def dfs(r: int, c: int) -> None: # Stop if out of bounds or water if r < 0 or r >= rows or c < 0 or c >= cols: return if grid[r][c] != '1': return # Mark as visited by "sinking" the land grid[r][c] = '0' # Explore all four directions dfs(r + 1, c) # down dfs(r - 1, c) # up dfs(r, c + 1) # right dfs(r, c - 1) # left # Scan every cell in the grid for r in range(rows): for c in range(cols): if grid[r][c] == '1': # Found new island! Count it and explore islands += 1 dfs(r, c) return islands explanation: | **Time Complexity:** O(m × n) — Each cell visited at most twice (once by loop, once by DFS). **Space Complexity:** O(m × n) — Recursion stack in worst case (grid is all land in a snake pattern). When we find unvisited land, we increment our count and use DFS to "sink" the entire island by marking all connected land as water. This prevents recounting. - approach_name: BFS is_optimal: true code: | from collections import deque def num_islands(grid: list[list[str]]) -> int: if not grid: return 0 rows, cols = len(grid), len(grid[0]) islands = 0 def bfs(start_r: int, start_c: int) -> None: queue = deque([(start_r, start_c)]) grid[start_r][start_c] = '0' # Mark starting cell while queue: r, c = queue.popleft() # Explore all four directions for dr, dc in [(1, 0), (-1, 0), (0, 1), (0, -1)]: nr, nc = r + dr, c + dc # Add unvisited land to queue if 0 <= nr < rows and 0 <= nc < cols and grid[nr][nc] == '1': grid[nr][nc] = '0' # Mark before adding to queue queue.append((nr, nc)) for r in range(rows): for c in range(cols): if grid[r][c] == '1': islands += 1 bfs(r, c) return islands explanation: | **Time Complexity:** O(m × n) — Same as DFS. **Space Complexity:** O(min(m, n)) — Queue holds at most one "frontier" layer, which is bounded by the smaller dimension. BFS explores level by level rather than depth-first. Mark cells as visited **when adding to queue** (not when processing) to avoid adding duplicates.