Loading problems...
TL;DR: The optimal solution utilizes Depth-First Search (DFS) to traverse connected components of land (0), counting a component only if it is completely surrounded by water (1) and does not touch the grid boundary.
In the Number of Closed Islands problem (LeetCode 1254), we are provided with a 2D grid representing a map where 0 represents land and 1 represents water. We need to identify the number of "closed islands." A closed island is defined as a group of connected 0s (land) that is totally surrounded by 1s (water) on all four sides (left, top, right, bottom).
Crucially, if a group of land cells connects to the edge of the grid, it is not considered a closed island because it is not surrounded by water on the boundary side.
A naive or brute force approach might involve iterating through every cell in the grid. Upon encountering a 0, one might attempt to verify if it belongs to a closed island by scanning outwards in all four directions until hitting water or the grid edge.
python# Pseudo-code for Naive Approach count = 0 for r in range(rows): for c in range(cols): if grid[r][c] == 0: if is_closed(r, c, visited_for_this_check): count += 1
The is_closed function would perform a traversal (like BFS or DFS). However, a naive implementation often fails to persist "visited" states globally. It might re-traverse the same land cells multiple times for different starting points, or fail to correctly propagate the "open" status (touching the boundary) back to the starting cell if the traversal logic is flawed.
Why it fails: While a correct brute force is essentially the optimal solution, inefficient implementations often lead to Time Limit Exceeded (TLE). If we do not mark cells as visited globally, the time complexity can degrade to because we repeatedly traverse the same components. Furthermore, checking rows and columns independently without a proper graph traversal fails to account for complex, snake-like island shapes.
The core insight connecting the naive approach to the optimal pattern is the definition of "closed." An island is closed if and only if none of its constituent cells lie on the boundary of the grid.
If a Depth-First Search (DFS) traversal starts at a land cell and reaches the edge of the grid, the entire connected component is invalid (not closed). If the traversal completes visiting all connected land cells and never hits the grid boundary, the island is valid.
Pattern Invariant:
We must ensure that we visit every node in a connected component to mark it as processed, even if we discover early on that the island is not closed. If we stop traversing immediately upon finding a boundary cell, we leave the rest of the component as 0s. The main loop would then encounter these unvisited parts later and incorrectly identify them as new, potentially valid islands.
Visual Description: Imagine the recursion tree expanding from a starting cell . As the DFS flows into neighbor cells , etc., it effectively "floods" the island. If any branch of this recursion tree touches the grid coordinates , , , or , a "false" flag propagates back up the tree. The algorithm effectively asks every branch: "Did you hit a wall (boundary)?" If all branches reply "No, I only hit water," the island is closed.

closed_islands = 0.r from 0 to rows-1, c from 0 to cols-1.grid[r][c] == 0:
dfs(grid, r, c).dfs returns true, increment closed_islands.dfs(grid, r, c):
grid[r][c] == 1, return true. This side is sealed by water.r or c are out of bounds (or on the boundary, depending on implementation style), this implies the island is touching the edge. Return false.grid[r][c] = 1 to mark as visited.dfs for up, down, left, right.isClosed = dfs(up) & dfs(down) & dfs(left) & dfs(right)&& in C++ or and in Python) immediately, or we might leave parts of the island unvisited.isClosed.closed_islands count.The algorithm is correct because the DFS traversal identifies Connected Components. By definition, a connected component is a maximal set of connected nodes.
0 1) ensures each component is processed exactly once.true if and only if every path from the starting node terminates at a 1 (water) without crossing the grid boundary. If any path hits the boundary, the false value propagates to the root of the call, preventing the increment of the counter.cppclass Solution { public: int closedIsland(vector<vector<int>>& grid) { int rows = grid.size(); int cols = grid[0].size(); int count = 0; for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { if (grid[i][j] == 0) { // If dfs returns true, it's a closed island if (dfs(grid, i, j, rows, cols)) { count++; } } } } return count; } private: bool dfs(vector<vector<int>>& grid, int r, int c, int rows, int cols) { // Check bounds: if we go out of bounds, it means the island touches the edge if (r < 0 || r >= rows || c < 0 || c >= cols) { return false; } // If we hit water (1), this path is closed if (grid[r][c] == 1) { return true; } // Mark as visited grid[r][c] = 1; // Visit all 4 directions. // IMPORTANT: We must visit ALL directions to mark the whole island. // Do not use short-circuiting && immediately. bool up = dfs(grid, r - 1, c, rows, cols); bool down = dfs(grid, r + 1, c, rows, cols); bool left = dfs(grid, r, c - 1, rows, cols); bool right = dfs(grid, r, c + 1, rows, cols); return up && down && left && right; } };
javaclass Solution { public int closedIsland(int[][] grid) { int rows = grid.length; int cols = grid[0].length; int count = 0; for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { if (grid[i][j] == 0) { if (dfs(grid, i, j, rows, cols)) { count++; } } } } return count; } private boolean dfs(int[][] grid, int r, int c, int rows, int cols) { // If out of bounds, the island touches the edge -> not closed if (r < 0 || r >= rows || c < 0 || c >= cols) { return false; } // If water (1), this direction is sealed if (grid[r][c] == 1) { return true; } // Mark current land as visited (turn to water) grid[r][c] = 1; // Traverse all directions // We use variables to ensure all recursive calls execute boolean up = dfs(grid, r - 1, c, rows, cols); boolean down = dfs(grid, r + 1, c, rows, cols); boolean left = dfs(grid, r, c - 1, rows, cols); boolean right = dfs(grid, r, c + 1, rows, cols); return up && down && left && right; } }
pythonclass Solution: def closedIsland(self, grid: List[List[int]]) -> int: rows, cols = len(grid), len(grid[0]) count = 0 def dfs(r, c): # If out of bounds, we hit the boundary -> not closed if r < 0 or r >= rows or c < 0 or c >= cols: return False # If water, this path is valid (sealed) if grid[r][c] == 1: return True # Mark visited grid[r][c] = 1 # IMPORTANT: Compute all directions individually. # Using `return dfs(...) and dfs(...)` directly would short-circuit, # potentially leaving parts of the island unvisited. up = dfs(r - 1, c) down = dfs(r + 1, c) left = dfs(r, c - 1) right = dfs(r, c + 1) return up and down and left and right for r in range(rows): for c in range(cols): if grid[r][c] == 0: if dfs(r, c): count += 1 return count
Time Complexity: where is the number of rows and is the number of columns. In the worst case, we visit every cell in the grid a constant number of times (once via the main loop, and potentially once via DFS).
Space Complexity:
This is the worst-case space required for the recursion stack. This occurs when the grid consists entirely of land (0s) or forms a snake-like pattern, causing the recursion depth to equal the number of cells.
The Graph DFS - Connected Components pattern is fundamental for many grid-based interview questions.
O to X. The logic is identical: find regions connected to the boundary and save them; flip everything else.Why does my solution return the wrong count for complex shapes?
&& in C++/Java or and in Python) directly in the return statement: return dfs(up) && dfs(down)....dfs(up) returns false, the language stops evaluating the expression. dfs(down) is never called.0. The main loop will encounter them later and count them as a new valid island, leading to double counting or incorrect validation.Why do I get a Stack Overflow or Infinite Loop?
grid[r][c] = 1) before making recursive calls.0s.Why does my logic fail on the grid corners?
if r == 0 inside the loop rather than handling it uniformly in the DFS base case.