Taro Logo

Number of Closed Islands

Medium
Google logo
Google
1 view
Topics:
ArraysGraphsRecursion

You are given a 2D grid consisting of 0s (land) and 1s (water). An island is a maximal 4-directionally connected group of 0s, and a closed island is an island completely (all left, top, right, bottom) surrounded by 1s. Write a function to return the number of closed islands.

Example 1:

Input: grid = [[1,1,1,1,1,1,1,0],[1,0,0,0,0,1,1,0],[1,0,1,0,1,1,1,0],[1,0,0,0,0,1,0,1],[1,1,1,1,1,1,1,0]]
Output: 2
Explanation: Islands in gray are closed because they are completely surrounded by water (group of 1s).

Example 2:

Input: grid = [[0,0,1,0,0],[0,1,0,1,0],[0,1,1,1,0]]
Output: 1

Example 3:

Input: grid = [[1,1,1,1,1,1,1],
              [1,0,0,0,0,0,1],
              [1,0,1,1,1,0,1],
              [1,0,1,0,1,0,1],
              [1,0,1,1,1,0,1],
              [1,0,0,0,0,0,1],
              [1,1,1,1,1,1,1]]
Output: 2

Clarifications:

  1. The grid will only contain 0s and 1s. How do I handle other values? (Assume the grid contains only 0s and 1s)
  2. What is the maximum size of the grid? (Assume 1 <= grid.length, grid[0].length <= 100)
  3. Do I need to handle invalid inputs, such as a null grid? (Yes, handle null or empty grids appropriately.)

Solution


Naive Approach

One straightforward approach is to iterate through each cell of the grid. If a cell is land (0), perform a Depth-First Search (DFS) or Breadth-First Search (BFS) to explore the entire island. During the exploration, check if the island touches the boundary of the grid. If it does, it's not a closed island. Count the islands that are completely surrounded by water.

Algorithm:

  1. Iterate through each cell in the grid.
  2. If a cell is land (0), initiate a DFS or BFS.
  3. During the search, mark visited cells to avoid revisiting.
  4. If any cell in the island lies on the boundary of the grid, mark the island as not closed.
  5. After exploring the island, increment the closed island count if it's a closed island.

Code (Python):

def closedIsland_naive(grid):
    if not grid:
        return 0

    rows, cols = len(grid), len(grid[0])
    visited = set()
    closed_islands = 0

    def dfs(i, j):
        if i < 0 or i >= rows or j < 0 or j >= cols or grid[i][j] == 1 or (i, j) in visited:
            return True  # Return True for water and visited cells

        visited.add((i, j))
        
        # Check if the current cell is on the boundary
        is_boundary = (i == 0 or i == rows - 1 or j == 0 or j == cols - 1)

        up = dfs(i - 1, j)
        down = dfs(i + 1, j)
        left = dfs(i, j - 1)
        right = dfs(i, j + 1)

        return not is_boundary and up and down and left and right

    for i in range(rows):
        for j in range(cols):
            if grid[i][j] == 0 and (i, j) not in visited:
                if dfs(i, j):
                    closed_islands += 1

    return closed_islands

Time Complexity:

  • O(m * n), where 'm' is the number of rows and 'n' is the number of columns in the grid. In the worst case, we might visit each cell once.

Space Complexity:

  • O(m * n) in the worst case due to the visited set and the recursion stack of DFS.

Optimal Approach

The optimal approach aims to reduce redundant checks by first identifying and eliminating islands that are not closed. This involves performing a DFS or BFS starting from the boundary cells. Any land connected to the boundary is, by definition, not a closed island. Mark these land cells as water (1) to effectively remove them from consideration. Then, count the remaining closed islands in the interior of the grid.

Algorithm:

  1. Iterate through the boundary cells of the grid. Specifically, the first and last rows and first and last columns.
  2. If a boundary cell is land (0), perform a DFS to convert the entire connected island to water (1).
  3. After eliminating all non-closed islands, iterate through the remaining grid.
  4. For each remaining land cell (0), perform a DFS to count the closed island.

Code (Python):

def closedIsland(grid):
    if not grid:
        return 0

    rows, cols = len(grid), len(grid[0])

    def dfs(i, j):
        if i < 0 or i >= rows or j < 0 or j >= cols or grid[i][j] == 1:
            return

        grid[i][j] = 1  # Mark as water

        dfs(i - 1, j)
        dfs(i + 1, j)
        dfs(i, j - 1)
        dfs(i, j + 1)

    # Eliminate islands connected to the boundary
    for i in range(rows):
        for j in range(cols):
            if i == 0 or i == rows - 1 or j == 0 or j == cols - 1:
                if grid[i][j] == 0:
                    dfs(i, j)

    # Count remaining closed islands
    closed_islands = 0
    for i in range(rows):
        for j in range(cols):
            if grid[i][j] == 0:
                closed_islands += 1
                dfs(i, j)  # Sink the island to avoid recounting

    return closed_islands

Time Complexity:

  • O(m * n), where 'm' is the number of rows and 'n' is the number of columns in the grid. In the worst case, we might visit each cell a constant number of times.

Space Complexity:

  • O(m * n) in the worst case due to the recursion stack of DFS.

Edge Cases

  • Empty Grid: If the grid is empty (either no rows or no columns), return 0.
  • All Water: If the entire grid is water (all 1s), return 0.
  • All Land: If the entire grid is land (all 0s), return 0 if the grid has any boundary, otherwise return 1.
  • Single Cell: A single cell grid will return 1 if it's land and surrounded by water.
  • Disconnected Islands: The algorithm should correctly handle multiple disconnected closed islands.