Making A Large Island
HardPrompt
You are given an n x n binary matrix grid. You are allowed to change at most one 0 to be 1.
Return the size of the largest island in grid after applying this operation.
An island is a 4-directionally connected group of 1s.
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.lengthn == grid[i].length1 <= n <= 500grid[i][j]is either0or1.
Approaches
2 approaches with complexity analysis and trade-offs.
The most straightforward approach is to simulate the process directly. We can iterate through every cell in the grid. If a cell contains a 0, we temporarily change it to a 1 and then calculate the size of the largest island in this newly modified grid. We keep track of the maximum size found across all these simulations.
Algorithm
- Initialize
maxSize = 0. - Initialize a boolean flag
hasZero = falseto track if any0exists in the grid. - Iterate through each cell
(r, c)of then x ngrid. - If
grid[r][c] == 0:- Set
hasZero = true. - Temporarily change
grid[r][c]to1. - Call a helper function
calculateLargestIsland(grid)to find the size of the largest island in the modified grid. This helper function uses a standard DFS/BFS traversal with avisitedarray to explore and count connected1s. - Update
maxSize = max(maxSize, calculatedSize). - Revert
grid[r][c]back to0to restore the grid for the next iteration.
- Set
- After the loops, if
hasZeroisfalse, it means the grid was all1s, so the answer isn*n. Otherwise, the answer ismaxSize.
Walkthrough
This method involves a nested loop to traverse the grid. For each cell (r, c) containing a 0, we perform the following steps:
- Flip
grid[r][c]from0to1. - Trigger a function to find the largest island size in the current state of the grid. This function itself would iterate through all cells, and for each unvisited
1, it would start a traversal (like Depth First Search or Breadth First Search) to find the size of the island it belongs to. Avisitedmatrix is required to avoid recounting. - Update a global maximum size variable with the result from step 2.
- Flip
grid[r][c]back to0to restore the grid for the next iteration.
An initial check is needed for the case where the grid contains no zeros. In this scenario, the answer is simply the size of the single island, which is n*n.
class Solution { public int largestIsland(int[][] grid) { int n = grid.length; int maxArea = 0; boolean hasZero = false; for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { if (grid[i][j] == 0) { hasZero = true; grid[i][j] = 1; // Flip 0 to 1 maxArea = Math.max(maxArea, findLargestIslandSize(grid)); grid[i][j] = 0; // Flip back } } } return hasZero ? maxArea : n * n; } private int findLargestIslandSize(int[][] grid) { int n = grid.length; boolean[][] visited = new boolean[n][n]; int maxArea = 0; for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { if (grid[i][j] == 1 && !visited[i][j]) { maxArea = Math.max(maxArea, dfs(i, j, grid, visited)); } } } return maxArea; } private int dfs(int r, int c, int[][] grid, boolean[][] visited) { int n = grid.length; if (r < 0 || r >= n || c < 0 || c >= n || visited[r][c] || grid[r][c] == 0) { return 0; } visited[r][c] = true; int count = 1; count += dfs(r + 1, c, grid, visited); count += dfs(r - 1, c, grid, visited); count += dfs(r, c + 1, grid, visited); count += dfs(r, c - 1, grid, visited); return count; }}Complexity
Time
O(n^4). Let `N = n*n`. There are `O(N)` cells. For each of the `O(N)` cells that could be a zero, we perform a full grid traversal to find the largest island, which takes `O(N)` time. This results in a total time complexity of `O(N*N) = O((n^2)^2) = O(n^4)`.
Space
O(n^2). The space is dominated by the `visited` array used in the island size calculation function and the recursion stack for DFS, both of which can be O(n^2) in the worst case.
Trade-offs
Pros
Simple to understand and implement.
Directly simulates the problem statement.
Cons
Highly inefficient due to redundant calculations.
For each
0that is flipped, it re-scans the entire grid and re-calculates island sizes from scratch.Will likely result in a "Time Limit Exceeded" error for larger grids as specified in the constraints.
A much more efficient approach involves two main passes over the grid. The first pass identifies all existing islands, assigns each a unique ID, and calculates and stores its size. The second pass iterates through all the 0s in the grid. For each 0, it checks its neighboring cells to see which unique islands it could connect. By summing the sizes of these unique neighboring islands and adding 1 (for the 0 itself), we can find the potential size of the new, larger island.
Algorithm
- Initialize a map
islandSizesto storeislandId -> sizeand anislandIdcounter starting from 2. - Pass 1: Labeling
- Iterate through each cell
(r, c)of the grid. - If
grid[r][c] == 1, it's a new, unlabeled island. - Start a DFS/BFS from
(r, c). - During the traversal, change all
1s of the island to the currentislandIdand count the number of cells (size). - Store the result:
islandSizes.put(islandId, size). - Increment
islandIdfor the next island.
- Iterate through each cell
- Handle edge case: If
islandSizesis empty after Pass 1, the grid was all0s. Return1(since we can flip one0). - Initialize
maxSizeto the largest value inislandSizes.values(). This covers the case where no0s can be flipped. - Pass 2: Merging
- Iterate through each cell
(r, c)of the grid. - If
grid[r][c] == 0:- Create a
Setto store the uniqueislandIds of its 4-directional neighbors. - Calculate
potentialSize = 1(for the current0). - For each unique
islandIdin the set, add its corresponding size from theislandSizesmap topotentialSize. - Update
maxSize = max(maxSize, potentialSize).
- Create a
- Iterate through each cell
- Return
maxSize.
Walkthrough
This method avoids the redundant calculations of the brute-force approach by pre-computing the sizes of all initial islands.
Pass 1: Find and Label Islands
We traverse the grid. When we encounter a 1 that hasn't been labeled, we start a traversal (DFS or BFS). We assign a new, unique islandId (e.g., starting from 2, to not conflict with 0 and 1). During the traversal, we find all connected 1s, count them to get the island's size, and update the grid cells of this island from 1 to islandId. We store the mapping of islandId to size in a hash map. We also find the largest island size found during this pass, which handles the edge case where the grid has no 0s.
Pass 2: Evaluate Zeros
We traverse the grid again, this time looking for 0s. For each 0, we look at its four neighbors and collect their unique islandIds. The potential new island size is 1 (for the 0 we flip) plus the sum of the sizes of all unique neighboring islands. We update our overall maximum size with this potential size.
class Solution { public int largestIsland(int[][] grid) { int n = grid.length; // map: islandId -> islandSize Map<Integer, Integer> islandSizes = new HashMap<>(); int islandId = 2; // Start island IDs from 2 // Pass 1: Find all islands, label them, and store their sizes. for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { if (grid[i][j] == 1) { // Found an unlabeled island int size = paint(i, j, grid, islandId); islandSizes.put(islandId, size); islandId++; } } } // If no islands were found, the grid is all zeros. The largest we can make is 1. if (islandSizes.isEmpty()) { return 1; } int maxArea = 0; // Get the size of the largest existing island. This handles the all-1s case. for (int size : islandSizes.values()) { maxArea = Math.max(maxArea, size); } // Pass 2: Iterate through zeros and calculate potential merged island size. for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { if (grid[i][j] == 0) { Set<Integer> neighborIds = new HashSet<>(); // Check 4-directional neighbors if (i > 0) neighborIds.add(grid[i - 1][j]); if (i < n - 1) neighborIds.add(grid[i + 1][j]); if (j > 0) neighborIds.add(grid[i][j - 1]); if (j < n - 1) neighborIds.add(grid[i][j + 1]); int potentialArea = 1; // For the zero itself for (int id : neighborIds) { if (id > 1) { // It's a valid island ID potentialArea += islandSizes.get(id); } } maxArea = Math.max(maxArea, potentialArea); } } } return maxArea; } // Helper function to find an island, paint it with an ID, and return its size. private int paint(int r, int c, int[][] grid, int islandId) { int n = grid.length; if (r < 0 || r >= n || c < 0 || c >= n || grid[r][c] != 1) { return 0; } grid[r][c] = islandId; return 1 + paint(r + 1, c, grid, islandId) + paint(r - 1, c, grid, islandId) + paint(r, c + 1, grid, islandId) + paint(r, c - 1, grid, islandId); }}Complexity
Time
O(n^2). Let `N = n*n`. Pass 1 involves a full grid traversal where each cell is visited once (DFS/BFS), taking `O(N)` time. Pass 2 involves another full grid traversal, and for each cell, the work is constant (checking 4 neighbors). This also takes `O(N)` time. The total time is `O(N) + O(N) = O(N) = O(n^2)`.
Space
O(n^2). The space is used for the `islandSizes` map, which can store up to O(n^2) entries in the worst case (a checkerboard pattern). The recursion stack for the DFS can also go up to O(n^2) in the worst case (a snake-like island).
Trade-offs
Pros
Very efficient, with a linear time complexity relative to the grid size.
Avoids redundant computations by pre-calculating and storing island properties.
Passes for large inputs where brute-force would fail.
Cons
More complex to implement than the brute-force approach.
Requires modifying the input grid or using an auxiliary grid, which increases space complexity if the input is immutable.
Solutions
Solution
class Solution {private int n;private int[] p;private int[] size;private int ans = 1;private int[] dirs = new int[]{-1, 0, 1, 0, -1};public int largestIsland(int[][] grid) { n = grid.length; p = new int[n * n]; size = new int[n * n]; for (int i = 0; i < p.length; ++i) { p[i] = i; size[i] = 1; } for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { if (grid[i][j] == 1) { for (int k = 0; k < 4; ++k) { int x = i + dirs[k], y = j + dirs[k + 1]; if (x >= 0 && x < n && y >= 0 && y < n && grid[x][y] == 1) { int pa = find(x * n + y), pb = find(i * n + j); if (pa == pb) { continue; } p[pa] = pb; size[pb] += size[pa]; ans = Math.max(ans, size[pb]); } } } } } for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { if (grid[i][j] == 0) { int t = 1; Set<Integer> vis = new HashSet<>(); for (int k = 0; k < 4; ++k) { int x = i + dirs[k], y = j + dirs[k + 1]; if (x >= 0 && x < n && y >= 0 && y < n && grid[x][y] == 1) { int root = find(x * n + y); if (!vis.contains(root)) { vis.add(root); t += size[root]; } } } ans = Math.max(ans, t); } } } return ans; }private int find(int x) { if (p[x] != x) { p[x] = find(p[x]); } return p[x]; }}class Solution {public: const static inline vector<int> dirs = {-1, 0, 1, 0, -1}; int largestIsland(vector<vector<int>> &grid) { int n = grid.size(); vector<int> p(n * n); vector<int> size(n * n, 1); iota(p.begin(), p.end(), 0); function<int(int)> find; find = [&](int x) { if (p[x] != x) { p[x] = find(p[x]); } return p[x]; }; int ans = 1; for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { if (grid[i][j]) { for (int k = 0; k < 4; ++k) { int x = i + dirs[k], y = j + dirs[k + 1]; if (x >= 0 && x < n && y >= 0 && y < n && grid[x][y]) { int pa = find(x * n + y), pb = find(i * n + j); if (pa == pb) continue; p[pa] = pb; size[pb] += size[pa]; ans = max(ans, size[pb]); } } } } } for (int i = 0; i < n; ++i) { for (int j = 0; j < n; ++j) { if (!grid[i][j]) { int t = 1; unordered_set<int> vis; for (int k = 0; k < 4; ++k) { int x = i + dirs[k], y = j + dirs[k + 1]; if (x >= 0 && x < n && y >= 0 && y < n && grid[x][y]) { int root = find(x * n + y); if (!vis.count(root)) { vis.insert(root); t += size[root]; } } } ans = max(ans, t); } } } return ans; }};class Solution: def largestIsland(self, grid: List[List[int]]) -> int: def find(x): if p[x] != x: p[x] = find(p[x]) return p[x] def union(a, b): pa, pb = find(a), find(b) if pa == pb: return p[pa] = pb size[pb] += size[pa] n = len(grid) p = list(range(n * n)) size = [1] * (n * n) for i, row in enumerate(grid): for j, v in enumerate(row): if v: for a, b in [[0, - 1], [- 1, 0]]: x, y = i + a, j + b if 0 <= x < n and 0 <= y < n and grid[x][y]: union(x * n + y, i * n + j) ans = max(size) for i, row in enumerate(grid): for j, v in enumerate(row): if v == 0: vis = set() t = 1 for a, b in [[0, - 1], [0, 1], [1, 0], [- 1, 0]]: x, y = i + a, j + b if 0 <= x < n and 0 <= y < n and grid[x][y]: root = find(x * n + y) if root not in vis: vis . add(root) t += size[root] ans = max(ans, t) return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.