Pacific Atlantic Water Flow

MediumGraphs · Reverse Multi-source DFS🧩 Graph DFSLeetCode #417 ↗Updated

Problem

An m × n island has cell heights heights[r][c]. The Pacific touches the top and left edges, the Atlantic the bottom and right edges. Rain can move from a cell to a 4-directional neighbour whose height is ≤ its own, and edge cells drain into the adjacent ocean. List every cell [r, c] from which water can reach both oceans (any order).
Input12235 / 32344 / 24531 / 67145 / 51124Output[[0,4],[1,3],[1,4],[2,2],[3,0],[3,1],[4,0]]
Input[[1]]Output[[0,0]]

1 ≤ m, n ≤ 200  ·  0 ≤ heights[r][c] ≤ 105

Try it yourself

Java
class Solution {
    public List<List<Integer>> pacificAtlantic(int[][] heights) {
        
    }
}
Your code is saved in this browser.Submit on LeetCode ↗
Key insight
Don't pour water from every cell — run it backwards. Start from each ocean's shore and climb to neighbours that are ≥ the current height; that marks all cells able to drain into that ocean. The answer is the intersection of the two marked sets.
PatternGraph DFS

Instinct Connected regions, all paths, reachability, cloning or 2-colouring a graph or grid: dive from a node, mark it, recurse into unvisited neighbours.

Memory hack Mark before you march: go deep, paint as you go, and back up only when every door is already painted.

Open the Graph DFS guide →

Approaches

Brute ForceSimulate Rain from Every Cell

Time O((m·n)2)Space O(m·n)

For each cell, DFS downhill (to neighbours with height ≤ current) using a fresh visited grid, noting whether the walk touches a Pacific edge (row 0 / col 0) and an Atlantic edge (last row / last col). Keep the cell if both were touched.

Java
static final int[][] DIRS = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
public List<List<Integer>> pacificAtlantic(int[][] heights) {
    int m = heights.length, n = heights[0].length;
    List<List<Integer>> res = new ArrayList<>();
    for (int r = 0; r < m; r++)
        for (int c = 0; c < n; c++) {
            boolean[] ocean = new boolean[2]; // [0] Pacific, [1] Atlantic
            flow(heights, r, c, new boolean[m][n], ocean);
            if (ocean[0] && ocean[1]) res.add(List.of(r, c));
        }
    return res;
}
private void flow(int[][] h, int r, int c, boolean[][] seen, boolean[] ocean) {
    seen[r][c] = true;
    if (r == 0 || c == 0) ocean[0] = true;
    if (r == h.length - 1 || c == h[0].length - 1) ocean[1] = true;
    if (ocean[0] && ocean[1]) return; // both reached: stop early
    for (int[] d : DIRS) {
        int x = r + d[0], y = c + d[1];
        if (x >= 0 && y >= 0 && x < h.length && y < h[0].length
                && !seen[x][y] && h[x][y] <= h[r][c]) flow(h, x, y, seen, ocean);
    }
}

Faithful to the statement, but every cell may flood the whole grid again.

At a glance

LevelApproachTimeSpace
Brute ForceSimulate Rain from Every CellO((m·n)2)O(m·n)
OptimalReverse DFS from Both OceansO(m·n)O(m·n)
Common pitfalls
  • Using > instead of ≥ — equal-height plateaus must be crossed.
  • Mixing up the shores: Pacific = top + left, Atlantic = bottom + right.
  • Memoising per-cell answers in the brute force — plateaus form cycles, so memo is wrong.
Interview tips
  • Say “reverse the direction of flow” — it turns m·n searches into two.
  • Explain why the inequality flips to ≥ when walking backwards.
  • Name it: multi-source graph search from the boundary, like Surrounded Regions.
Pattern
Reverse multi-source flood fill → “which cells can reach target X”: search from X instead.
My notes saved in this browser