LeetCode 1254: Number of Closed Islands — Finding the Idea Before Writing the Code (Java)

Struggling with LeetCode 1254 Number of Closed Islands? Follow a beginner-friendly journey from understanding a grid to discovering recursion, DFS, and how to count islands completely surrounded by water in Java.

Krishna Shrivastava
12 views
LinkedInGithubX
0
0
LeetCode 1254: Number of Closed Islands — Finding the Idea Before Writing the Code (Java)
Listen to articleAudio version

The Honest Beginning: I Understood the Question, but I Didn't Know How to Solve It

Problem: LeetCode 1254 — Number of Closed Islands

When I first read this question, I understood the basic meaning of the input. We have a grid containing 0s and 1s. Zero represents land, and one represents water.

But understanding the input and knowing how to solve the problem are two completely different things.

The question asks us to count closed islands. What exactly is an island here? How do we decide whether several zeroes belong to the same island? And how can we tell whether an island is closed?

I didn't immediately think of DFS, recursion, or connected components. Honestly, I didn't even know which direction to take.

So instead of jumping straight into a solution, let's start with something familiar and build the intuition from there.

Step 1: Imagine Looking for Land on a Map

Imagine that you are looking at a small map made up of land and water.

  1. 0 means land.
  2. 1 means water.

Suppose several pieces of land are touching each other horizontally or vertically. Even if that land occupies many grid cells, we consider it one connected island.

For example, look at this grid:

1 1 1 1 1
1 0 0 1 1
1 1 0 1 1
1 1 1 1 1

There are three land cells, but they are not three separate islands.

The two zeroes in the second row are connected. The zero directly below the second zero is also connected to them.

Therefore, all three cells form one island.

This gives us our first important observation:

We should not count every zero individually. We need to discover groups of zeroes connected in four directions: up, down, left, and right.

Notice that diagonal contact does not count. Two land cells touching only at their corners are not connected under this problem's rules.

But we still haven't answered the most important question: how do we discover an entire group?

Step 2: How Do We Explore All the Connected Land?

Suppose we find a zero at position (1, 1).

What should we do next?

We can inspect its four neighboring cells:

  1. Left: (1, 0)
  2. Right: (1, 2)
  3. Up: (0, 1)
  4. Down: (2, 1)

If a neighboring cell contains land, we should explore that cell too. Then we should inspect its neighbors, and continue until there is no more unvisited land connected to our starting point.

Think about exploring a maze. You enter a room, check the available paths, enter another room, and continue exploring. Whenever you reach a dead end or a path you have already visited, you return and try another direction.

That is very close to what recursion does.

We can write a function that explores a cell and calls itself for the four neighboring cells. This technique is commonly called Depth-First Search (DFS).

I initially found the recursive calls confusing because it felt as if the function would keep calling itself forever. The missing piece is that we need a way to remember which cells have already been explored.

In this problem, we can use the grid itself.

Whenever we visit a land cell, we change its value from 0 to 1. Since we have finished treating that cell as unvisited land, we won't explore it again.

This prevents us from repeatedly visiting the same cells and getting stuck in an infinite recursive loop.

Step 3: Exploring an Island Is Not Enough — Is It Closed?

At this point, we can discover an island. But the problem doesn't ask us to count every island. It asks us to count only closed islands.

Imagine two pieces of land.

In the first case, land is surrounded by water on every side. It is completely enclosed.

In the second case, some land reaches the edge of the map. This island is open because it is not completely surrounded by water.

The important distinction is that an island touching the boundary of the grid cannot be closed.

For example:

1 1 1 1 1
1 0 0 1 1
1 1 0 1 1
1 1 1 1 1

The island is surrounded by water, so it is closed.

Now consider:

0 0 1 1 1
1 0 0 1 1
1 1 1 1 1

The land group touches the top edge of the grid. It is not closed, even though some of its cells have water around them.

This gives us the second important observation:

An island is closed only if none of its connected land cells touches the boundary of the grid.

We now have the complete plan:

  1. Find an unvisited land cell.
  2. Explore every land cell connected to it.
  3. Determine whether that entire island touches the boundary.
  4. Count it only if it is closed.
  5. Continue looking for other unvisited islands.

The remaining challenge is implementing this logic without losing information about whether the island is open or closed.

Step 4: Translating the Idea into Recursion

Let's design a recursive function called sol().

It receives the grid and the current row and column. It returns a boolean:

  1. true means the explored land group is closed.
  2. false means the group touches the grid boundary and is not closed.

The function needs to handle three situations.

Case 1: We reach water

If the current cell contains 1, we have reached water. This direction does not lead to additional land outside the island.

We return true because this particular direction does not make the island open.

Case 2: We reach the grid boundary

If the current cell is land and lies on the outermost row or column, the island touches the boundary.

That means the island is not closed, so this exploration must eventually return false.

However, there is a subtle implementation detail here: we must still mark and explore the boundary-connected land correctly. If we stop immediately at the first boundary cell without exploring the rest of that connected group, some of the island's cells may remain unvisited. A later scan could then mistake part of that same open island for a separate closed island.

We will address this in the final implementation.

Case 3: We reach unvisited interior land

This is where recursion does the real work.

We mark the current cell as visited by changing it to 1. Then we explore all four directions.

The island is closed only if every direction confirms that it does not connect to the outside boundary.

In boolean terms, the results must be combined using logical AND (&&).

If even one path reaches boundary-connected land, the whole island is open.

Step 5: Connecting the Recursive Function to the Main Loop

We need a way to start exploring islands.

The simplest approach is to scan the grid using two nested loops.

The outer loop moves through the rows, and the inner loop moves through the columns. Whenever we find an unvisited land cell, we call our recursive function.

If the function confirms that the island is closed, we increment our answer.

Why can we modify the grid instead of creating a separate visited matrix?

Because the original problem only requires us to return the count. We don't need to preserve the original grid, so changing visited land into water is a convenient way to remember our progress.

We also need to avoid counting the same island twice. Once an entire connected island has been explored and marked, its cells will no longer trigger another DFS call from the main loop.

Java Solution

Here is the complete implementation. It follows the recursive DFS approach described above and handles boundary-connected islands correctly.

class Solution {
public boolean sol(int[][] grid, int row, int col) {
// Reaching outside the grid means this island is open.
if (row < 0 || row >= grid.length ||
col < 0 || col >= grid[0].length) {
return false;
}

// Water does not make the island open.
if (grid[row][col] == 1) {
return true;
}

// Mark this land cell as visited.
grid[row][col] = 1;

// Explore all four directions.
boolean left = sol(grid, row, col - 1);
boolean right = sol(grid, row, col + 1);
boolean top = sol(grid, row - 1, col);
boolean bottom = sol(grid, row + 1, col);

return left && right && top && bottom;
}

public int closedIsland(int[][] grid) {
int rows = grid.length;
int cols = grid[0].length;
int ans = 0;

for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (grid[i][j] == 0) {
if (sol(grid, i, j)) {
ans++;
}
}
}
}

return ans;
}
}

What changed from my first attempt?

My original code checked whether the current cell was water before checking whether it was on the boundary. It also returned immediately when isperim() detected a boundary land cell.

That creates a subtle problem: the boundary cell remains unvisited, and the DFS does not continue through it to explore the rest of the connected island. Consequently, land connected through the boundary can be left behind and potentially counted incorrectly later.

The revised code uses a different strategy:

  1. It checks whether the coordinates are outside the grid before accessing grid[row][col].
  2. It treats water as a successful direction.
  3. It marks every visited land cell, including boundary land.
  4. It explores all four directions before combining their results.

This ensures that the whole connected island is visited, even when it is open.

One more detail matters: we store the results of all four recursive calls in separate variables. This is intentional. Writing a short-circuit expression that skips later calls after the first false could leave some cells unexplored.

Step 6: Dry Run

Consider this grid:

1 1 1 1 1
1 0 0 1 1
1 1 0 1 1
1 1 1 1 1

The main loop scans the grid until it finds the first zero at (1, 1).

First, we start DFS at (1, 1).

The cell is land, so we mark it as visited. The function explores its left, right, top, and bottom neighbors.

The right neighbor (1, 2) is also land. The DFS visits it and explores its neighbors.

The cell (2, 2) is land as well, so the exploration continues there.

Eventually, every connected land cell has been visited.

Next, we check the four directions.

All paths eventually reach water or the outside of the grid through no unvisited land. Because no connected land cell reaches the boundary, the recursive result is true.

We increment the answer:

ans = 1

The main loop continues scanning. Since the three land cells have already been changed to 1, they are not counted again.

There are no other land cells, so the final answer is:

1

This small example demonstrates the entire mechanism: one DFS call discovers the complete island, marks it visited, and returns whether it is closed.

A More Important Edge Case: One Island Touching the Boundary

Consider this grid:

0 0 1 1 1
1 0 0 1 1
1 1 1 1 1

The connected land group touches the top boundary.

When DFS starts from one of its land cells, it will eventually reach a coordinate outside the grid. That recursive call returns false.

The false result propagates through the recursive calls, so the complete island is classified as open and is not counted.

Because the DFS still explores all four directions and marks every land cell, the main loop cannot later count another portion of this same island as a closed island.

The answer is 0.

This is why visiting the complete connected group matters just as much as deciding whether it is closed.

Complexity Analysis

Let m be the number of rows and n be the number of columns.

Time complexity: O(m × n)

Each cell is visited at most once because every visited land cell is changed from 0 to 1. The grid scan also examines every cell. Therefore, the total time complexity is O(m × n).

Auxiliary space complexity: O(m × n) in the worst case.

The recursive DFS can reach a depth proportional to the number of cells if a large island is connected throughout the grid. The recursion call stack can therefore use O(m × n) space in the worst case.

The algorithm modifies the input grid directly and does not require a separate visited matrix.

What I Learned from This Problem

The biggest lesson was that I didn't need to know the term “connected component” before starting. I needed to understand what the problem was asking me to discover.

First, I noticed that individual zeroes were not the answer; groups of connected zeroes were. That led to the idea of exploring neighbors.

Then I realized that exploring one neighbor should lead to exploring its neighbors too. Recursion gave me a way to express that repeated process.

Finally, I understood that a closed island was not just a group of land cells. It was a group that never reached the boundary.

The difficult part was not writing four recursive calls. It was making sure the function explored the entire island and correctly communicated whether any part of it touched the boundary.

That distinction is worth remembering in future DFS problems: discovering a connected group and evaluating a property of that group are two related, but separate, tasks.

Final Takeaway

LeetCode 1254 becomes easier to reason about once we stop thinking of it as a mysterious graph problem and start thinking of it as exploring connected land on a map.

  1. Use DFS to explore the entire island.
  2. Mark visited land so it cannot be counted twice.
  3. Treat a path that reaches outside the grid as evidence that the island is open.
  4. Count an island only when every explored direction confirms that it is closed.

If you struggled to find this approach on your first attempt, that is a normal part of learning DSA. The goal is not to recognize every pattern immediately. It is to learn how to turn an unfamiliar problem into a sequence of smaller, understandable decisions.

Ai Assistant Kas