Making A Large Island

Updated on 06 June, 2025
Making A Large Island header image

Problem Statement

In computational geometry and graph theory, we often deal with the concept of connectivity on grids. In this problem, you are given a square binary matrix grid of size n x n, where each cell contains either a 0 or a 1. The challenge is to identify the largest possible "island" or contiguous block of 1s after you are permitted to change at most one 0 to a 1. An island is defined as a group of 1s that are connected horizontally or vertically (4-directionally). This task tests your ability to manipulate and analyze two-dimensional arrays, optimizing for connectivity after a single, strategic modification.

Examples

Example 1

Input:

grid = [[1,0],[0,1]]

Output:

3

Explanation:

Change one 0 to 1 and connect two 1s, then we get an island with area = 3.

Example 2

Input:

grid = [[1,1],[1,0]]

Output:

4

Explanation:

Change the 0 to 1 and make the island bigger, only one island with area = 4.

Example 3

Input:

grid = [[1,1],[1,1]]

Output:

4

Explanation:

Can't change any 0 to 1, only one island with area = 4.

Constraints

  • n == grid.length
  • n == grid[i].length
  • 1 <= n <= 500
  • grid[i][j] is either 0 or 1.

Approach and Intuition

The problem involves identifying and potentially expanding the largest contiguous group of 1s in a binary matrix, with the allowance to convert one 0 to 1. Here is a step-by-step approach to solve the problem:

  1. Identify All Islands:

    • Use a depth-first search (DFS) or breadth-first search (BFS) to traverse the grid and identify all distinct islands. During this process, mark the cells that belong to the same island with a unique identifier.
  2. Calculate Island Sizes:

    • While identifying islands, maintain a count of the number of 1s in each island. Store these sizes in a dictionary or array, where the key/index represents the unique identifier of the island and the value represents its size.
  3. Consider the Conversion of 0 to 1:

    • For every 0 in the grid, imagine converting it to 1. Check all four possible 4-directional neighbors of this cell:
      • If a neighbor is a 1, note down which island this neighbor belongs to.
      • Assess whether converting this 0 connects two or more different islands.
  4. Calculate the Potential Max Size:

    • For each 0, sum the sizes of all unique neighboring islands. Add 1 (representing the 0 that could be converted). This sum reflects the potential new size of an island if the 0 at this location is converted to 1.
  5. Determine the Maximum:

    • Track the maximum island size achieved by converting each 0. Compare it to the largest existing island size to determine the overall maximum size possible.

The approach essentially involves two major sweeps over the grid: one for identifying islands and their sizes, and another for evaluating the impact of changing each 0 to a 1 on the size of contiguous 1s. This method ensures that all possibilities are considered, leveraging the spatial relationships in a grid to derive the optimal solution.

Solutions

  • C++
  • Java
  • Python
cpp
class UnionFind {
public:
    vector<int> leader;
    vector<int> rank;

    UnionFind(int n) {
        leader.resize(n);
        rank.resize(n, 1);
        for (int i = 0; i < n; i++) {
            leader[i] = i;
        }
    }

    int find(int x) {
        if (leader[x] != x) {
            leader[x] = find(leader[x]);
        }
        return leader[x];
    }

    void unite(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);
        if (rootX == rootY) return;
        if (rank[rootX] < rank[rootY]) {
            leader[rootX] = rootY;
            rank[rootY] += rank[rootX];
        } else {
            leader[rootY] = rootX;
            rank[rootX] += rank[rootY];
        }
    }
};

class Solution {
public:
    int largestIsland(vector<vector<int>>& grid) {
        int m = grid.size();
        int n = grid[0].size();
        UnionFind uf(m * n);
        vector<int> dr = {0, 0, 1, -1};
        vector<int> dc = {1, -1, 0, 0};

        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (grid[i][j] == 1) {
                    int id = n * i + j;
                    for (int d = 0; d < 4; ++d) {
                        int ni = i + dr[d];
                        int nj = j + dc[d];
                        if (ni >= 0 && ni < m && nj >= 0 && nj < n && grid[ni][nj] == 1) {
                            int nid = n * ni + nj;
                            uf.unite(id, nid);
                        }
                    }
                }
            }
        }

        int maxSize = 0;
        bool zeroPresent = false;
        unordered_set<int> uniqueRoots;
        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (grid[i][j] == 0) {
                    zeroPresent = true;
                    int newSize = 1;
                    for (int d = 0; d < 4; ++d) {
                        int ni = i + dr[d];
                        int nj = j + dc[d];
                        if (ni >= 0 && ni < m && nj >= 0 && nj < n && grid[ni][nj] == 1) {
                            int nid = n * ni + nj;
                            int root = uf.find(nid);
                            if (uniqueRoots.insert(root).second) {
                                newSize += uf.rank[root];
                            }
                        }
                    }
                    maxSize = max(maxSize, newSize);
                    uniqueRoots.clear();
                }
            }
        }

        return zeroPresent ? maxSize : m * n;
    }
};

The C++ solution for the problem "Making A Large Island" involves creating a class UnionFind to manage the union-find algorithm, which is used to group adjacent lands (represented as 1s in a grid) into islands. Each entry in the grid is initially treated as its own separate set in the UnionFind structure. This class has methods to find the root representative (find) and to unite two sets (unite).

The main logic for determining the largest possible island size is implemented in the largestIsland method of the Solution class:

  1. Initialize a UnionFind instance to cover the total number of cells in the grid.
  2. Traverse the grid to connect adjacent land cells. This effectively builds the 'island groups' or connected components.
  3. For each water cell (represented as 0), consider converting it to land and calculate the potential size of the resulting island by uniting it with its adjacent land cells if any.
  4. Keep track of the maximal possible size during the process.

The algorithm uses variables to manage indices and ensure bounds checking as it examines adjacent cells. It also maintains uniqueness checks via a set (uniqueRoots) to avoid double-counting the areas of connected components during the size calculation for possible islands after a conversion of a water cell to land.

The solution returns the size of the largest island after considering conversions. If there are no water cells (zeroPresent is false), then the entire grid is already a single large island, and its size (m * n) is returned. Otherwise, the maximum size found during the cell conversion process is returned.

java
class UnionFind {
    
    public int[] leader;
    public int[] size;

    public UnionFind(int n) {
        leader = new int[n];
        size = new int[n];
        for (int i = 0; i < n; i++) {
            leader[i] = i;
            size[i] = 1;
        }
    }

    public int find(int x) {
        if (leader[x] != x) {
            leader[x] = find(leader[x]);
        }
        return leader[x];
    }

    public void union(int x, int y) {
        int rootX = find(x);
        int rootY = find(y);

        if (rootX == rootY) return;

        if (size[rootX] < size[rootY]) {
            leader[rootX] = rootY;
            size[rootY] += size[rootX];
        } else {
            leader[rootY] = rootX;
            size[rootX] += size[rootY];
        }
    }
}

class MaxIslandFinder {

    public int maxIslandArea(int[][] grid) {
        int r = grid.length;
        int c = grid[0].length;
        UnionFind uf = new UnionFind(r * c);

        int[] dr = {1, -1, 0, 0};
        int[] dc = {0, 0, 1, -1};

        for (int i = 0; i < r; i++) {
            for (int j = 0; j < c; j++) {
                if (grid[i][j] == 1) {
                    int currentIdx = i * c + j;
                    for (int d = 0; d < 4; d++) {
                        int ni = i + dr[d];
                        int nj = j + dc[d];

                        if (ni >= 0 && nj >= 0 && ni < r && nj < c && grid[ni][nj] == 1) {
                            int neighborIdx = ni * c + nj;
                            uf.union(currentIdx, neighborIdx);
                        }
                    }
                }
            }
        }

        int maxArea = 0;
        boolean zeroFound = false;
        Set<Integer> uniqueRoots = new HashSet<>();

        for (int i = 0; i < r; i++) {
            for (int j = 0; j < c; j++) {
                if (grid[i][j] == 0) {
                    zeroFound = true;
                    int tentativeSize = 1;

                    for (int d = 0; d < 4; d++) {
                        int ni = i + dr[d];
                        int nj = j + dc[d];
                        if (ni >= 0 && nj >= 0 && ni < r && nj < c && grid[ni][nj] == 1) {
                            int root = uf.find(ni * c + nj);
                            uniqueRoots.add(root);
                        }
                    }

                    for (int root : uniqueRoots) {
                        tentativeSize += uf.size[root];
                    }
                    uniqueRoots.clear();
                    maxArea = Math.max(maxArea, tentativeSize);
                }
            }
        }

        return zeroFound ? maxArea : r * c;
    }
}

The Java implementation focuses on solving the "Making A Large Island" problem. This solution entails determining the largest possible island that can be formed by converting one zero cell into a one on a 2D grid, which represents land (1) and water (0). The code leverages a Union-Find data structure to efficiently perform union and find operations to manage and merge islands.

  • UnionFind Class:

    • Attributes: Contains leader array for tracking the leader of each set, and size array for tracking the size of each set.
    • Constructor: Initializes each grid cell to be its own leader and sets the size to 1.
    • Find Method: Recursively finds the leader of a set, employing path compression for efficiency.
    • Union Method: Unites two sets, attaching the smaller tree under the larger tree to maintain shorter trees.
  • MaxIslandFinder Class:

    • Method maxIslandArea(int[][] grid) Method: Computes the largest possible island:
      • Loop through each grid cell, applying union operations for adjacent land cells to form distinct islands under a common leader.
      • Traverse grid cells again and for every water cell (0), check its four neighbors. If they are land, perform union-find operations to evaluate potential island size if the water cell were turned into land.
      • Keep track of max possible island area that considers merging adjacent distinct islands.
      • The method returns either the maximum size found or the total grid size if no water cell is present.

This approach efficiently groups cells, calculates potential maximum areas through strategic merging, and uses path compression and union by rank to optimize time complexity. The use of a Union-Find structure is particularly effective for problems involving the connectivity of components on a grid, making this solution both optimal and practical for large grids.

python
class UnionFind:
    def __init__(self, size: int):
        self.parent = list(range(size))
        self.component_size = [1] * size

    def find(self, elem: int) -> int:
        if self.parent[elem] != elem:
            self.parent[elem] = self.find(self.parent[elem])
        return self.parent[elem]

    def union(self, elem1: int, elem2: int):
        root1 = self.find(elem1)
        root2 = self.find(elem2)

        if root1 == root2:
            return
        
        if self.component_size[root1] < self.component_size[root2]:
            self.parent[root1] = root2
            self.component_size[root2] += self.component_size[root1]
        else:
            self.parent[root2] = root1
            self.component_size[root1] += self.component_size[root2]


class IslandFinder:
    def maxIslandArea(self, grid: list[list[int]]) -> int:
        row_count = len(grid)
        col_count = len(grid[0])

        uf = UnionFind(row_count * col_count)

        directions = [(0, 1), (0, -1), (1, 0), (-1, 0)]
        
        for row in range(row_count):
            for col in range(col_count):
                if grid[row][col] == 1:
                    node_index = row * col_count + col
                    
                    for d in directions:
                        adj_row, adj_col = row + d[0], col + d[1]
                        
                        if 0 <= adj_row < row_count and 0 <= adj_col < col_count and grid[adj_row][adj_col] == 1:
                            adj_index = adj_row * col_count + adj_col
                            uf.union(node_index, adj_index)

        max_area = 0
        no_zero = True
        root_set = set()

        for row in range(row_count):
            for col in range(col_count):
                if grid[row][col] == 0:
                    no_zero = False
                    potential_area = 1
                    root_set.clear()
                    
                    for d in directions:
                        adj_row, adj_col = row + d[0], col + d[1]
                        
                        if 0 <= adj_row < row_count and 0 <= adj_col < col_count and grid[adj_row][adj_col] == 1:
                            adj_index = adj_row * col_count + adj_col
                            root = uf.find(adj_index)
                            if root not in root_set:
                                root_set.add(root)
                                potential_area += uf.component_size[root]
                    
                    max_area = max(max_area, potential_area)
        
        if no_zero:
            return row_count * col_count
        
        return max_area

The solution for the "Making A Large Island" challenge implements a two-part approach using Python 3. First, it uses the Union-Find data structure to manage and merge connected components of the island on a grid. Each cell on the grid is initially treated as an independent component. The Union-Find structure helps efficiently find and merge these components when they are connected, i.e., when there are adjacent island cells (1s on the grid).

  • The UnionFind class defined includes methods for initialization, finding the root of a component, and union operations to merge components.
  • The find function ensures path compression which helps in flattening the structure of the tree almost to its roots, thus speeding up future operations.
  • The union function merges two components by size - connecting the smaller component under the root of the bigger one, which helps in keeping the tree flatter and operations efficient.

The second part of the solution is managed by the IslandFinder class with the maxIslandArea function. This function calculates the maximum area of an island that can be formed by flipping one zero to one:

  1. The function iterates through each cell in the grid, identifying cells that are part of the land (1).
  2. For each land cell, it checks adjacent cells in four possible directions (up, down, left, right).
  3. Using the Union-Find structure, it connects current land cells with adjacent land cells.
  4. After building the connected components, the function checks each water cell (0), calculates the possible island size by converting this single cell to land and merging it with any adjacent land components.
  5. It keeps track of the maximum possible island size found during this entire process.

Additional considerations:

  • The maxIslandArea function handles the edge case where the grid has no zeros. In such a scenario, where the grid is entirely land, the maximum area is simply the total number of cells in the grid.
  • The solution ensures that during the area calculation for each water cell, it does not overcount the area by using a set to keep track of unique root components of adjacent land.

With this implementation, the solution effectively and efficiently calculates the maximum possible island area after flipping one cell, considering grid connectivity via the Union-Find structure and iterating over possible grid transformations using adjacency checks and component merging.

Comments

No comments yet.