Topics › Graphs › Problems on BFS/DFS
Pacific Atlantic Water Flow
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
You are given an m x n grid heights of an island. The Pacific Ocean touches the island’s top and left edges; the Atlantic Ocean touches its bottom and right edges. Rain falling on a cell flows to any of its four side neighbours whose height is less than or equal to the current cell’s height. Water in a cell on the border flows straight into the ocean that border touches.
Return every cell [r, c] from which rain can reach both oceans. The Java methods list them in row-major order (by row, then column).
Constraints: 1 ≤ m, n ≤ 200, 0 ≤ heights[r][c] ≤ 10^5.
Example 1
Pacific (top and left)
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
Atlantic (bottom and right)
answer = [[0,4],[1,3],[1,4],[2,2],[3,0],[3,1],[4,0]]
For example, (2,2) with height 5 can flow up to (1,2) = 3, then (0,2) = 2 on the top edge (Pacific), and also right to (2,3) = 3, then (2,4) = 1 on the right edge (Atlantic).
Example 2
heights = [[1]]
answer = [[0,0]] // a single cell touches all four edges
Note that “flow” allows equal heights: on a flat grid every cell can reach every border.
Approach 1: Brute force, simulate the rain from every cell
Intuition. Take the question literally. For each cell, find every cell its water can reach by repeatedly moving to a neighbour that is not higher, using a BFS. While doing so, record whether the water ever stands on a top/left border cell (Pacific) or a bottom/right border cell (Atlantic). If both happen, the cell belongs in the answer. The search can stop early once both oceans are found.
Algorithm.
- For each cell
(r, c): BFS with a freshseengrid, moving only to neighbours withheight <= current height. - Set
pacificif a visited cell hasx == 0ory == 0, andatlanticifx == m - 1ory == n - 1. - If both flags are set, add
[r, c]to the result.
static List<List<Integer>> bruteForce(int[][] heights) {
int m = heights.length, n = heights[0].length;
List<List<Integer>> result = new ArrayList<>();
for (int r = 0; r < m; r++) {
for (int c = 0; c < n; c++) {
boolean pacific = false, atlantic = false;
boolean[][] seen = new boolean[m][n];
Deque<int[]> queue = new ArrayDeque<>();
seen[r][c] = true;
queue.add(new int[]{r, c});
while (!queue.isEmpty() && !(pacific && atlantic)) {
int[] cell = queue.poll();
int x = cell[0], y = cell[1];
if (x == 0 || y == 0) pacific = true; // top or left edge
if (x == m - 1 || y == n - 1) atlantic = true; // bottom or right edge
for (int d = 0; d < 4; d++) {
int nx = x + DR[d], ny = y + DC[d];
if (nx < 0 || ny < 0 || nx >= m || ny >= n || seen[nx][ny]) continue;
if (heights[nx][ny] <= heights[x][y]) { // water moves to an equal or lower cell
seen[nx][ny] = true;
queue.add(new int[]{nx, ny});
}
}
}
if (pacific && atlantic) result.add(List.of(r, c));
}
}
return result;
}
Complexity. Time O((m · n)^2): one BFS of up to m · n cells for each of the m · n cells. With 200 x 200 that is 1.6 · 10^9 steps in the worst case. Space O(m · n) for one seen grid and queue.
The waste is that neighbouring cells repeat nearly the same search. And we are asking the question from the wrong end: there are m · n sources of rain but only two destinations.
Approach 2: Optimal, reverse the flow from each ocean
Intuition. Instead of asking “where can water from this cell go?”, ask “which cells can send water into this ocean?” Water flows from a cell A into a neighbour B when height[B] <= height[A]. Reverse every arrow: from B we may step back to A whenever height[A] >= height[B], that is, we climb to neighbours that are at least as high.
Start a BFS from all Pacific border cells at once (top row and left column). They reach the Pacific trivially. Climbing uphill from them reaches exactly the cells whose water can flow down into one of them, and so into the Pacific. Do the same from the Atlantic border (bottom row and right column). The answer is the set of cells marked by both searches.
This is a multi-source BFS: all border cells start in the queue together, and a shared reach grid ensures each cell is processed at most once per ocean, however many border cells could reach it.
Algorithm.
- Mark and enqueue every cell in column 0 and row 0 for the Pacific, and every cell in column
n - 1and rowm - 1for the Atlantic. - For each ocean, BFS: from a cell, step to an in-bounds, unmarked neighbour whose height is
>=the current height; mark and enqueue it. - Collect the cells marked in both grids, in row-major order.
static List<List<Integer>> optimal(int[][] heights) {
int m = heights.length, n = heights[0].length;
boolean[][] pac = new boolean[m][n], atl = new boolean[m][n];
Deque<int[]> pq = new ArrayDeque<>(), aq = new ArrayDeque<>();
for (int r = 0; r < m; r++) {
mark(pac, pq, r, 0); // left edge touches the Pacific
mark(atl, aq, r, n - 1); // right edge touches the Atlantic
}
for (int c = 0; c < n; c++) {
mark(pac, pq, 0, c); // top edge
mark(atl, aq, m - 1, c); // bottom edge
}
climb(heights, pac, pq);
climb(heights, atl, aq);
List<List<Integer>> result = new ArrayList<>();
for (int r = 0; r < m; r++) {
for (int c = 0; c < n; c++) {
if (pac[r][c] && atl[r][c]) result.add(List.of(r, c));
}
}
return result;
}
static void mark(boolean[][] reach, Deque<int[]> queue, int r, int c) {
if (reach[r][c]) return;
reach[r][c] = true;
queue.add(new int[]{r, c});
}
/** Multi-source BFS that only steps to neighbours at least as high: those are cells whose water could flow down to us. */
static void climb(int[][] heights, boolean[][] reach, Deque<int[]> queue) {
int m = heights.length, n = heights[0].length;
while (!queue.isEmpty()) {
int[] cell = queue.poll();
for (int d = 0; d < 4; d++) {
int nr = cell[0] + DR[d], nc = cell[1] + DC[d];
if (nr < 0 || nc < 0 || nr >= m || nc >= n || reach[nr][nc]) continue;
if (heights[nr][nc] >= heights[cell[0]][cell[1]]) mark(reach, queue, nr, nc);
}
}
}
Dry run on a small grid [[1, 2], [4, 3]]:
| ocean | border seeds | climbs to | reachable set |
|---|---|---|---|
| Pacific | (0,0)=1, (0,1)=2, (1,0)=4 | from (0,1)=2 up to (1,1)=3 | all four cells |
| Atlantic | (0,1)=2, (1,0)=4, (1,1)=3 | (0,0)=1 is lower than both neighbours, never reached | (0,1), (1,0), (1,1) |
Intersection: [[0,1],[1,0],[1,1]]. Cell (0,0) is on the Pacific border, but its water is trapped below heights 2 and 4, so it never reaches the Atlantic. The brute force agrees: from (0,0) there is no allowed move at all.
Complexity. Time O(m · n): each search marks every cell at most once and checks four neighbours per cell, and the final intersection is one more pass. Space O(m · n) for the two boolean grids and the queues.
Edge cases and pitfalls
- Equal heights flow. The rule is
<=going downhill, so the reverse search must use>=going uphill. Using strict comparisons drops whole flat regions; the[[3,3],[3,3]]test catches this. - Get the direction right. The reverse search climbs. Writing
<=in the reverse BFS computes where water from the coast would go, which is a different, wrong set. - Corners belong to both borders.
(0, n-1)and(m-1, 0)touch both oceans directly. A single row or a single column puts every cell on both borders. - Mark when enqueuing. Border cells are marked as they are seeded, and the
markhelper skips cells already marked, so corner cells and cells reachable from many seeds are processed once. - Pits. A low cell in the middle surrounded by higher land cannot reach any ocean; the tests include a grid with a 0 in the centre.
- Output order. LeetCode accepts any order. Collecting by scanning rows then columns gives a deterministic order that is easy to test.
- Recursion. A DFS version of the reverse search is common and equally correct, but on a
200 x 200staircase it can recurse 40,000 levels deep; BFS avoids that.
Related problems
- Number of islands(Do in Grid and Graph Both) uses the same four-direction grid traversal.
- Surrounded Regions also starts from the border and marks everything reachable from it.
- Number of enclaves is a border-first multi-source search on a binary grid.
- Rotten Oranges is another multi-source BFS.