Problem overview
A rectangular island lies between two oceans. The Pacific touches its left and top edges, and the Atlantic touches its right and bottom edges. The island is the grid heights with m rows and n columns, and heights[r][c] is the height of the cell in row r and column c. Water flows from a cell to a neighbour (up, down, left or right) whose height is less than or equal to the cell's, and from any cell on an edge into the ocean that touches that edge.
The method pacificAtlantic returns the cells from which water can reach both oceans, as a List<List<Integer>> of [r, c] pairs in reading order: row by row from the top, left to right within a row.
For the 5 by 5 grid in the first example, the answer is [[0, 4], [1, 3], [1, 4], [2, 2], [3, 0], [3, 1], [4, 0]]. The cell [0, 4] is on the top edge and on the right edge, so it touches both oceans. For [[1]] the answer is [[0, 0]], since the only cell touches all four edges. For the grid of 5s around a single 1, the answer is every cell except the middle one: water moves between equal heights, so the border passes it along to both oceans, while the 1 is lower than all its neighbours and its water stays put.
The statement asks for O(m · n) time and O(m · n) extra space, with grids up to 200 by 200.
Approach: climb up from each ocean
The obvious way is to follow the water downhill from every cell, one search per cell. That can cost O((m · n)²): picture a large flat lake in the middle, where each cell's search wanders over the whole lake before giving up.
The better idea turns the question around. Start from the ocean and walk against the flow. A breadth-first search begins at every cell along the Pacific's two edges at once and moves to a neighbour only when the neighbour is at least as high, because water from that neighbour can flow down into the current cell and onward to the ocean. Every cell the search reaches can send its water to the Pacific. A second search does the same from the Atlantic's edges. The answer is the set of cells that both searches reach, listed in reading order.
Each search visits each cell at most once, so two searches plus one pass over the grid are linear in the number of cells. For the first example, the cells marked 1 below are those the Pacific search reaches, then those the Atlantic search reaches:
pacific atlantic
11111 00001
11111 00011
11100 00111
11000 11111
10000 11111
The cells marked in both grids are exactly the seven listed in the answer. The cell [2, 1], height 4, is marked for the Pacific only: it reaches the Pacific through [2, 0], but every path to the Atlantic needs a step up.
Step by step
- Store the grid size in
rows(heights.length) andcols(heights[0].length). Every row has the same length. - Find the Pacific cells with
reach(heights, 0, 0), stored inpacific. The helper is written afterwards. It takes an edge row and an edge column and marks every cell whose water can flow to them. The Pacific touches row 0 and column 0. - Find the Atlantic cells with
reach(heights, rows - 1, cols - 1), stored inatlantic. One helper serves both oceans, and only the edges differ. - Create
result, aList<List<Integer>>backed by anArrayList. - Loop over the rows with
r. Going row by row, and left to right within a row, produces the required order. - Loop over the columns with
c. - Add the cell as
Arrays.asList(r, c)when bothpacific[r][c]andatlantic[r][c]are true. - Return
result. - Write
reach, takingheights,edgeRowandedgeColand returning aboolean[][]. It climbs up from the ocean with a BFS that only moves to cells at least as high. - Store
rowsandcolsagain. The variables ofpacificAtlanticaren't visible inside the helper. - Create
seen, aboolean[rows][cols]that starts all false. A cell becomestrueonce the search has reached it. - Create
queue, aDeque<int[]>backed by anArrayDeque. Each element is a cell, anint[]holding its row and column. - Loop over the rows to seed the search from the whole ocean border at once.
- Loop over the columns.
- Test
r == edgeRow || c == edgeCol: a cell on the edge row or the edge column touches the ocean. - Mark and queue it: set
seen[r][c]totrueand addnew int[]{r, c}to the queue. Marking at the moment of queueing ensures no cell is queued twice. - Define
directions, the four moves as row and column changes. Each move changes only one of them, so diagonal cells aren't neighbours. - Loop while the queue isn't empty.
- Take the front cell with
pollintocell. - Copy its values into
rowandcol, which read better thancell[0]andcell[1]. - Loop over
directionswith an enhancedfor. - Compute the neighbour:
nextRowisrow + d[0]andnextColiscol + d[1]. - Check that it is inside the grid before using it as an index.
- Check that the search can climb to it: it isn't seen yet and its height is at least the current cell's. This is the key line: the search walks against the flow.
- Mark and queue the neighbour. Its water reaches this ocean too, and its own neighbours are checked when it leaves the queue.
- Return
seenafter the loop. Atrueentry means the cell's water reaches this ocean.
The solution in Java
class Solution {
public List<List<Integer>> pacificAtlantic(int[][] heights) {
int rows = heights.length;
int cols = heights[0].length;
boolean[][] pacific = reach(heights, 0, 0);
boolean[][] atlantic = reach(heights, rows - 1, cols - 1);
List<List<Integer>> result = new ArrayList<>();
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
if (pacific[r][c] && atlantic[r][c]) {
result.add(Arrays.asList(r, c));
}
}
}
return result;
}
boolean[][] reach(int[][] heights, int edgeRow, int edgeCol) {
int rows = heights.length;
int cols = heights[0].length;
boolean[][] seen = new boolean[rows][cols];
Deque<int[]> queue = new ArrayDeque<>();
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
if (r == edgeRow || c == edgeCol) {
seen[r][c] = true;
queue.add(new int[]{r, c});
}
}
}
int[][] directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
while (!queue.isEmpty()) {
int[] cell = queue.poll();
int row = cell[0];
int col = cell[1];
for (int[] d : directions) {
int nextRow = row + d[0];
int nextCol = col + d[1];
if (nextRow >= 0 && nextRow < rows && nextCol >= 0 && nextCol < cols) {
if (!seen[nextRow][nextCol] && heights[nextRow][nextCol] >= heights[row][col]) {
seen[nextRow][nextCol] = true;
queue.add(new int[]{nextRow, nextCol});
}
}
}
}
return seen;
}
}
The Java details worth a closer look:
- Two-dimensional arrays.
new boolean[rows][cols]createsrowsarrays ofcolsbooleans, allfalse.heights[0].lengthis the width because each row is its own array. - Cells as
int[]. The queue holds small arrays of two integers. That avoids writing a class for a cell, at the price of readingcell[0]andcell[1]. Arrays.asList(r, c)builds a list from its arguments. Theintvalues are boxed toInteger, and the result is a fixed-size list, which is fine for a pair that is never changed. The lesson's editor already providesjava.util. In a file of your own, addimport java.util.*;.- Array initializer.
int[][] directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};is allowed in a declaration and lets the four neighbours be handled by one loop. - Bounds check first. The check of
nextRowandnextColsits in its ownif, around the one that readsseenandheights. Skipping it would throw anArrayIndexOutOfBoundsExceptionat the edges. - Helper results.
reachreturns theseenarray itself, which the caller then reads. Arrays are objects, so this returns a reference and copies nothing.
Complexity
Each call of reach queues every cell at most once, because a cell is marked when it joins the queue. For each queued cell it looks at 4 neighbours, so one search is O(m · n). Two searches and the final pass over the grid keep the total at O(m · n). The two seen grids, the queue and the result use O(m · n) extra space.
Edge cases and common mistakes
- Using
>instead of>=. Water also flows between cells of equal height, and the search must climb to equal cells. With a strict comparison, the grid of 5s around the 1 would lose cells such as[1, 2], which reaches the Pacific only through its equal neighbour[0, 2]. - Climbing the wrong way. The search moves to neighbours that are at least as high, not lower ones. Following the water downhill from the ocean's edge gives the wrong set.
- A single row. Every cell is on both the top and the bottom edge, so all of them reach both oceans. In
[[1, 2, 3]]all three cells are returned. - Marking cells late. Marking a cell when it leaves the queue, not when it joins, can queue the same cell many times.
- Mixing up row and column in the edge test. The condition
r == edgeRow || c == edgeColcovers a whole row and a whole column. For the Pacific those are row 0 and column 0, and for the Atlantic they are the last row and the last column. - Pits. A cell lower than all its neighbours, like the 1 in the middle of the grid of 5s, is never reached by the climbing searches, so it stays out of the answer.
Testing
These results come from running the solution:
heights | Result |
|---|---|
[[1, 2, 2, 3, 5], [3, 2, 3, 4, 4], [2, 4, 5, 3, 1], [6, 7, 1, 4, 5], [5, 1, 1, 2, 4]] | [[0, 4], [1, 3], [1, 4], [2, 2], [3, 0], [3, 1], [4, 0]] |
[[1]] | [[0, 0]] |
[[5, 5, 5], [5, 1, 5], [5, 5, 5]] | [[0, 0], [0, 1], [0, 2], [1, 0], [1, 2], [2, 0], [2, 1], [2, 2]] |
[[1, 2, 3]] | [[0, 0], [0, 1], [0, 2]] |
[[3], [2], [1]] | [[0, 0], [1, 0], [2, 0]] |
[[1, 2], [4, 3]] | [[0, 1], [1, 0], [1, 1]] |
A flat 200 by 200 grid returns all 40,000 cells.
