83.Pacific Atlantic Water Flow
Problem
An m x n island is described by heights, where heights[r][c] is the height of cell (r, c). The Pacific Ocean touches the island's top and left edges; the Atlantic Ocean touches its bottom and right edges.
Rain water can flow from a cell to a 4-directionally adjacent cell if that neighbour's height is less than or equal to the current height. Water flows from any cell on an edge directly into the ocean beside that edge.
Return a list of grid coordinates [r, c] from which rain water can reach both the Pacific and the Atlantic. The order of the result does not matter.
Examples
Input: heights = [
[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]
]
Output: [[0,4],[1,3],[1,4],[2,2],[3,0],[3,1],[4,0]]
Explanation: e.g. from (2,2) (height 5) water flows up 5→3→2 to the top edge (Pacific) and right 5→3→1 to the right edge (Atlantic).Input: heights = [[1]]
Output: [[0,0]]
Explanation: A single cell touches all four edges, so it drains into both oceans.Input: heights = [
[1,2,3],
[8,9,4],
[7,6,5]
]
Output: [[0,2],[1,0],[1,1],[1,2],[2,0],[2,1],[2,2]]
Explanation: (0,0) and (0,1) are surrounded by higher cells on the Atlantic side, so they only reach the Pacific.Constraints
m == heights.length,n == heights[r].length1 <= m, n <= 2000 <= heights[r][c] <= 10^5
💡 Key Insight
Asking "where can water from this cell go?" for every cell repeats the same searches. Flip the direction: start at the oceans and walk uphill (to neighbours with height >= the current one). Every cell reached from the Pacific border can drain into the Pacific, and likewise for the Atlantic. Run one multi-source search per ocean, then return the cells marked by both. Each search visits each cell at most once, so the total is O(m·n).
Brute Force
TimeO((m·n)²)SpaceO(m·n)Intuition
Simulate the rain directly. From each cell, explore every cell water could flow to (neighbours with height <= the current cell). If that exploration touches the top/left edge the cell reaches the Pacific; if it touches the bottom/right edge it reaches the Atlantic. Stop as soon as both are confirmed.
Algorithm
- For each cell
(r, c):- BFS downhill with a fresh
visitedarray. - For every reached cell, set
pacific = trueif its row is0or column is0, andatlantic = trueif its row ism - 1or column isn - 1. - If both are true, add
[r, c]to the result.
- BFS downhill with a fresh
- Return the result.
Visualization
heights start (0,1) = 2, flow to cells with height <= 2
1 2 3
8 9 4 reached: (0,1) → (0,0)=1
7 6 5 (0,0) touches top/left → Pacific ✓
no reached cell on bottom row / right column → Atlantic ✗
→ (0,1) not in answerCode
import java.util.*;
class Solution {
private static final int[][] DIRS = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
public List<List<Integer>> pacificAtlantic(int[][] heights) {
int rows = heights.length, cols = heights[0].length;
List<List<Integer>> result = new ArrayList<>();
for (int r = 0; r < rows; r++) {
for (int c = 0; c < cols; c++) {
if (reachesBoth(heights, r, c)) {
result.add(Arrays.asList(r, c));
}
}
}
return result;
}
// BFS downhill from (r, c) and see which ocean edges are touched
private boolean reachesBoth(int[][] heights, int r, int c) {
int rows = heights.length, cols = heights[0].length;
boolean[][] visited = new boolean[rows][cols];
Deque<int[]> queue = new ArrayDeque<>();
queue.offer(new int[]{r, c});
visited[r][c] = true;
boolean pacific = false, atlantic = false;
while (!queue.isEmpty()) {
int[] cell = queue.poll();
int cr = cell[0], cc = cell[1];
if (cr == 0 || cc == 0) pacific = true;
if (cr == rows - 1 || cc == cols - 1) atlantic = true;
if (pacific && atlantic) return true;
for (int[] d : DIRS) {
int nr = cr + d[0], nc = cc + d[1];
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && !visited[nr][nc]
&& heights[nr][nc] <= heights[cr][cc]) { // water flows down or level
visited[nr][nc] = true;
queue.offer(new int[]{nr, nc});
}
}
}
return false;
}
}Complexity
- Time: O((m·n)²) — a BFS of up to
m·ncells is launched from each of them·ncells. - Space: O(m·n) —
visitedand the queue for one search.
Reverse DFS from the Oceans
TimeO(m·n)SpaceO(m·n)Intuition
Water flowing downhill from X to the ocean is the same path as walking uphill from the ocean to X. So run a DFS from every Pacific border cell, moving only to neighbours whose height is >= the current cell, and mark everything reached in pacific[][]. Do the same from the Atlantic border into atlantic[][]. A shared visited matrix per ocean ensures each cell is expanded at most once per ocean.
Algorithm
- Create
pacificandatlanticboolean matrices. - For each row
r:dfs(r, 0, pacific)anddfs(r, n - 1, atlantic). - For each column
c:dfs(0, c, pacific)anddfs(m - 1, c, atlantic). dfs(r, c, ocean): markocean[r][c]; recurse into in-bounds, unmarked neighbours withheights[nr][nc] >= heights[r][c].- Collect every cell where both matrices are
true.
Dry Run
heights pacific reach atlantic reach both
1 2 3 P P P . . A . . X
8 9 4 P P P A A A X X X
7 6 5 P P P A A A X X X
Pacific: from (0,2)=3 climb 4 → 5 → 6 → 7 and 6 → 9; left column and top row are borders.
Atlantic: from (2,0)=7 climb to 8 → 9; (0,1)=2 and (0,0)=1 are lower than every Atlantic-side neighbour.Code
import java.util.*;
class Solution {
private static final int[][] DIRS = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
private int[][] heights;
private int rows, cols;
public List<List<Integer>> pacificAtlantic(int[][] heights) {
this.heights = heights;
rows = heights.length;
cols = heights[0].length;
boolean[][] pacific = new boolean[rows][cols];
boolean[][] atlantic = new boolean[rows][cols];
for (int r = 0; r < rows; r++) {
dfs(r, 0, pacific); // left edge
dfs(r, cols - 1, atlantic); // right edge
}
for (int c = 0; c < cols; c++) {
dfs(0, c, pacific); // top edge
dfs(rows - 1, c, atlantic); // bottom edge
}
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;
}
// Walk uphill from an ocean, marking every cell that can drain into it
private void dfs(int r, int c, boolean[][] ocean) {
if (ocean[r][c]) return;
ocean[r][c] = true;
for (int[] d : DIRS) {
int nr = r + d[0], nc = c + d[1];
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols
&& !ocean[nr][nc] && heights[nr][nc] >= heights[r][c]) {
dfs(nr, nc, ocean);
}
}
}
}Complexity
- Time: O(m·n) — each ocean's DFS marks every cell at most once and checks 4 neighbours.
- Space: O(m·n) — two boolean matrices plus recursion depth up to
m·non a monotone snake-shaped terrain.
Reverse Multi-Source BFSOptimal
TimeO(m·n)SpaceO(m·n)Intuition
Same reversal, but each ocean is processed with one multi-source BFS: all of its border cells start in the queue at once. This keeps the O(m·n) bound and removes the deep recursion that a 200 x 200 grid (40,000 cells) could otherwise cause.
Algorithm
- Seed
pacificQueuewith every cell in row0and column0; seedatlanticQueuewith every cell in rowm - 1and columnn - 1. Mark seeds in their ocean matrix. bfs(queue, ocean): pop a cell; for each in-bounds neighbour that is unmarked and has height>=the current height, mark it and enqueue it.- Run
bfsfor both oceans. - Return every cell marked in both matrices.
Dry Run
On [[1,2,3],[8,9,4],[7,6,5]], the Atlantic BFS:
flowchart TD
S["seed Atlantic: (2,0)=7 (2,1)=6 (2,2)=5<br/>(1,2)=4 (0,2)=3"] --> A["pop (2,0)=7<br/>(1,0)=8 >= 7 → mark"]
A --> B["pop (2,1)=6<br/>(1,1)=9 >= 6 → mark"]
B --> C["pop (2,2), (1,2): nothing new<br/>pop (0,2)=3: (0,1)=2 is lower ✗"]
C --> D["pop (1,0)=8: (0,0)=1 is lower ✗<br/>pop (1,1)=9: nothing new"]
D --> E["Atlantic = all except (0,0), (0,1)"]
E --> F["Pacific BFS reaches all 9 cells"]
F --> G["answer = 7 cells in both"]Code
import java.util.*;
class Solution {
private static final int[][] DIRS = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
public List<List<Integer>> pacificAtlantic(int[][] heights) {
int rows = heights.length, cols = heights[0].length;
boolean[][] pacific = new boolean[rows][cols];
boolean[][] atlantic = new boolean[rows][cols];
Deque<int[]> pacQueue = new ArrayDeque<>();
Deque<int[]> atlQueue = new ArrayDeque<>();
// Seed each ocean with its border cells
for (int r = 0; r < rows; r++) {
seed(pacQueue, pacific, r, 0);
seed(atlQueue, atlantic, r, cols - 1);
}
for (int c = 0; c < cols; c++) {
seed(pacQueue, pacific, 0, c);
seed(atlQueue, atlantic, rows - 1, c);
}
bfs(heights, pacQueue, pacific);
bfs(heights, atlQueue, atlantic);
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;
}
private void seed(Deque<int[]> queue, boolean[][] ocean, int r, int c) {
if (!ocean[r][c]) { // corners belong to two edges; add once
ocean[r][c] = true;
queue.offer(new int[]{r, c});
}
}
// Multi-source BFS walking uphill (or level) away from the ocean
private void bfs(int[][] heights, Deque<int[]> queue, boolean[][] ocean) {
int rows = heights.length, cols = heights[0].length;
while (!queue.isEmpty()) {
int[] cell = queue.poll();
int r = cell[0], c = cell[1];
for (int[] d : DIRS) {
int nr = r + d[0], nc = c + d[1];
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols
&& !ocean[nr][nc] && heights[nr][nc] >= heights[r][c]) {
ocean[nr][nc] = true;
queue.offer(new int[]{nr, nc});
}
}
}
}
}Complexity
- Time: O(m·n) — each cell is enqueued at most once per ocean.
- Space: O(m·n) — the two ocean matrices and the queues.
Complexity Comparison
| Approach | Time | Space |
|---|---|---|
| Brute Force | O((m·n)²) | O(m·n) |
| Reverse DFS | O(m·n) | O(m·n) |
| Reverse Multi-Source BFS | O(m·n) | O(m·n) |
Common Pitfalls
- Using the downhill comparison (
<=) in the reverse search — from the ocean you must climb, so the neighbour must satisfyheights[nr][nc] >= heights[r][c]. - Using strict
>and missing flat plateaus; water flows between cells of equal height. - Mixing up the edges: Pacific is top row + left column, Atlantic is bottom row + right column. Corner
(0, n-1)and(m-1, 0)touch both oceans. - Sharing one
visitedmatrix between the two oceans — a cell reached from the Pacific would then never be explored from the Atlantic. - Memoizing per-cell results in the brute force with plain DFS; cycles between equal-height cells make naive memoization return wrong answers.