Problem overview
The grid is a two-dimensional int[,] in which 1 is land and 0 is water. An island is a group of land cells connected horizontally or vertically. Diagonal neighbours do not connect. SizeOfIslands returns a List<int> holding the size of each island, in any order. The edges of the grid are assumed to be water, but the code below doesn't depend on that.
In the first example, all nine land cells are linked, so the result is [9]. In the second example the grid has a block of four cells, a single cell, and a pair of cells, so the sizes are 4, 1 and 2. This lesson's code scans the grid row by row, so it reports them as [4, 1, 2].
Approach: scan, flood, count
Scan every cell in row order. When you meet a land cell that you haven't seen, you have found a new island. Then run a depth-first search from that cell that visits every land cell connected to it and counts them. The count is the island's size.
To avoid visiting a cell twice, and to avoid counting the same island again later in the scan, the search turns each land cell it visits into water. The scan then skips those cells automatically. This "sink as you go" trick means no separate visited array is needed, at the price of changing the grid you were given.
The search is recursive. From a land cell it calls itself on the four neighbours. A neighbour that is outside the grid or already water returns at once, so the recursion stops at the island's border.
Step by step
- Declare an
intfieldnoOfCellson the class. The recursive search adds to it at every level, so it lives outside the method. - In
SizeOfIslands, createresult, an emptyList<int>. - Write two nested
forloops. The outer one runsifrom 0 togrid.GetLength(0)(rows) and the inner one runsjup togrid.GetLength(1)(columns). - If
grid[i, j] == 1, you have found an unvisited island. - Reset
noOfCellsto 0, so it counts only this island. - Call
Dfs(grid, i, j). On return, every cell of the island is water andnoOfCellsholds its size. - Add
noOfCellstoresult. - After both loops, return
result. - Write
Dfs(int[,] grid, int row, int col)as avoidmethod. First, ifIsValidCellsays the cell isn't a usable land cell, return immediately. - Otherwise count the cell with
noOfCells++and setgrid[row, col] = 0. Doing the second part before recursing is what stops two adjacent land cells from calling each other forever. - Define
directionsas four offsets: up, down, left and right. Loop over them and callDfson(row + d[0], col + d[1]). Out-of-range and water neighbours are rejected by the check at the top. - Write
IsValidCellas one boolean expression: the row is in range, the column is in range, and the cell holds 1. The range checks come first, and&&stops evaluating at the first false part, sogrid[row, col]is never read with a bad index.
The solution in C#
class Solution {
public bool IsValidCell(int[,] grid, int row, int col) {
return row >= 0 && row < grid.GetLength(0) &&
col >= 0 && col < grid.GetLength(1) &&
grid[row, col] == 1;
}
public void Dfs(int[,] grid, int row, int col) {
if (!IsValidCell(grid, row, col)) {
return;
}
noOfCells++;
grid[row, col] = 0;
int[][] directions = new int[][] { new int[]{-1, 0}, new int[]{1, 0}, new int[]{0, -1}, new int[]{0, 1} };
foreach (int[] d in directions) {
Dfs(grid, row + d[0], col + d[1]);
}
}
int noOfCells;
public List<int> SizeOfIslands(int[,] grid) {
List<int> result = new List<int>();
for (int i = 0; i < grid.GetLength(0); ++i) {
for (int j = 0; j < grid.GetLength(1); ++j) {
if (grid[i, j] == 1) {
noOfCells = 0;
Dfs(grid, i, j);
result.Add(noOfCells);
}
}
}
return result;
}
}
Trace the second example. The scan finds land at (0, 0). Dfs counts it, sinks it, and spreads to (1, 0), (0, 1) and (1, 1), so the first island has 4 cells. Later the scan reaches (2, 2), a lone land cell: all four neighbours are water or out of range, so the size is 1. Finally it reaches (3, 3), whose right neighbour (3, 4) is land, and the size is 2.
Some C# details:
int[,]is a rectangular array. You read a cell withgrid[i, j], notgrid[i][j]. It has no.Lengthper row.GetLength(0)is the number of rows andGetLength(1)the number of columns. (grid.Lengthwould be the total cell count.)- Jagged versus rectangular.
directionsis a jagged array,int[][], where each element is its ownint[]. It is created withnew int[][] { new int[]{-1, 0}, ... }. - The grid is modified. Arrays are reference types, so
Dfschanges the caller's array, not a copy. AfterSizeOfIslandsruns, the grid is all water. Copy it first with(int[,])grid.Clone()if you need the original. - A field as a counter.
noOfCellsis an instance field, which C# initialises to 0 and which every call shares. An alternative is to makeDfsreturn a count:1 + the sum of the four recursive calls. - Method order. C# lets a method call another that is declared later in the class, so
IsValidCellandDfscan sit above or below their callers.
Complexity
Time: O(R * C) for a grid with R rows and C columns. The scan touches each cell once. Each land cell is entered by the search once and turned to water, and every call does a constant amount of work, with four neighbour checks.
Space: O(R * C) in the worst case, for the recursion depth. A grid that is all land can make the search go as deep as the number of cells. The result list is smaller than that.
Edge cases and common mistakes
- No land. An all-water grid returns an empty list.
- Diagonal cells. In
{{1,0},{0,1}}the two land cells touch only at a corner, so they are two islands of size 1: the result is[1, 1]. - Forgetting to sink the cell. If
Dfsdoesn't set the cell to 0, the recursion never ends: each cell calls its neighbour, which calls it back. - Not resetting the counter. Without
noOfCells = 0before each search, each size includes the previous islands' cells. - Swapping the bounds checks. Reading
grid[row, col]before checking the range throwsIndexOutOfRangeExceptionat the edges. - Mixing up rows and columns.
GetLength(0)bounds the row index andGetLength(1)the column index. On a non-square grid, swapping them goes out of range.
Testing
- Rows
11110 / 11010 / 11000 / 00000give[9]. - Rows
11000 / 11000 / 00100 / 00011give[4, 1, 2]. - Rows
00 / 00give[]. - Rows
10 / 01give[1, 1].