Algorithm

Pacific Atlantic Water Flow

Breadth-First Search (BFS) Pattern

Pacific Atlantic Water Flow

An m x n rectangular island is described by a grid of cell heights. The Pacific Ocean touches the island's top and left edges; the Atlantic Ocean touches its bottom and right edges.

Rain falls on every cell. Water flows from a cell to any of its 4 orthogonal neighbours whose height is less than or equal to the current cell's height, and flows off the island from any border cell into the ocean that edge touches.

Return the coordinates of every cell from which water can reach both oceans. The result may be in any order.

CONSTRAINTS
  • m == heights.length, n == heights[r].length
  • 1 <= m, n <= 200
  • 0 <= heights[r][c] <= 10⁵
  • Water moves only up, down, left and right — never diagonally.
  • Equal heights are passable in both directions.
EXAMPLE 1
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]]
Take (2,2), height 5. Going left through 4 then 2 reaches the Pacific edge, and going right through 3 then 1 reaches the Atlantic edge — both descents are non-increasing. By contrast (0,0), height 1, touches the Pacific directly but is walled in by 2 and 3, so water there can never descend toward the Atlantic.
EXAMPLE 2
Input: heights = [[1]]
Output: [[0,0]]
A single cell sits on every edge at once, so it touches both oceans without any flow at all. Border cells drain into their adjacent ocean by definition.
EXAMPLE 3
Input: heights = [[2,1],[1,2]]
Output: [[0,0],[0,1],[1,0],[1,1]]
Every cell here is on both a Pacific edge and an Atlantic edge — the top row and left column touch the Pacific, the bottom row and right column touch the Atlantic — so all four qualify regardless of their heights.
EXAMPLE 4
Input: heights = [[1,2,3],[8,9,4],[7,6,5]]
Output: [[0,2],[1,2],[2,0],[2,1],[2,2],[1,0],[1,1]]
The 9 at the centre drains both ways: down the descending spiral 9,8,7 to the left edge for the Pacific, and 9,4,5 to the right edge for the Atlantic. The corner (0,0) holding 1 is the lowest cell on the island, so nothing can flow out of it toward the Atlantic — being low is a disadvantage here, not an advantage.
Can water flow between two cells of equal height?
Yes — the rule is less than or equal. That makes equal-height regions fully connected in both directions, so a flat plateau touching either edge drains entirely.
Do border cells automatically reach the ocean they touch?
Yes, water runs straight off the edge with no height comparison. So the top-left cell always reaches the Pacific, and the bottom-right always reaches the Atlantic, whatever their heights.
Are the two paths to the two oceans allowed to be different?
Completely different. A cell qualifies if some downhill route reaches the Pacific and some — possibly unrelated — downhill route reaches the Atlantic. They need not share a single step.
Does the order of the returned coordinates matter, and can a cell appear twice?
Order is free and each qualifying cell is listed once. Since the answer is a set of positions, deduplication matters if you collect results from two separate traversals.

Two words in this statement carry the difficulty: both and each. We need, for every cell on the island, an answer to two separate reachability questions. Notice what is not being asked — no distance, no shortest route, no count of steps. Just: can water get there at all? Keeping that straight decides which tool to use, and it is the first thing worth settling.

The direct approach, and the shape of its waste

Take one cell, pour water on it, and follow every downhill route until you either fall off a Pacific edge, fall off an Atlantic edge, or run out of moves. Record whether each ocean was reached. Then repeat for the next cell.

python
def pacific_atlantic(heights):
    result = []
    for r in range(rows):
        for c in range(cols):
            if reaches_pacific(r, c) and reaches_atlantic(r, c):   # a full search each
                result.append([r, c])
    return result

It is correct, and it is quadratic. A single downhill exploration can sweep almost the whole island — picture a long descending ramp — so each of the m × n cells can cost up to m × n work, giving O((m × n)²). At the limit of 200 × 200 that is 40,000 cells each potentially touching 40,000 cells: 1.6 billion steps.

But the running time is not the most interesting problem with it. The repetition is. Consider two neighbouring cells that both drain toward the same valley. Their explorations trace nearly the same route, discover the same descent, and reach the same edge. Every cell on a mountainside re-derives the identical fact that the mountainside drains to the sea. The same downhill corridors are being walked over and over by different starting points, all heading toward the same handful of destinations.

That phrasing points at the fix, and it is the same move that rescues many-to-few problems generally: when thousands of searchers are converging on a few places, stop searching from the searchers and start searching from the places.

The reframe: walk uphill from the water

There are only two destinations that matter — the Pacific and the Atlantic. So instead of asking which cells drain into the Pacific, ask the mirror question: which cells can the Pacific reach if we walk backwards?

Reverse every rule. Water flows from a cell to a neighbour of equal or lower height. Traversed backwards, we move from a cell to a neighbour of equal or higher height. Start on the ocean's edge and climb.

The equivalence needs stating carefully, because it is the entire justification: a route from cell A down to the ocean exists if and only if the same sequence of cells, read in reverse, is a valid climb from the ocean up to A. Each individual step reverses cleanly — if height[X] is at least height[Y] then walking from Y to X is a legal uphill step — so a whole downhill route reverses into a whole uphill route, and vice versa. Nothing is lost or gained by the reversal; the two questions have exactly the same answer set.

So: start a single traversal seeded with every Pacific border cell at once, climb, and mark everything reachable. Every marked cell drains to the Pacific. Do the same from every Atlantic border cell. Then the cells appearing in both marked sets are the answer.

Seeding all border cells simultaneously rather than running one traversal per border cell is worth naming — it is a multi-source traversal, and it is not many searches but one search whose starting layer happens to have many members. Its cost is the same as a single search: each cell is claimed once, by whichever source reaches it, and since we only care whether a cell was reached and not from which source, having many origins costs nothing extra.

Two passes over the grid, each O(m × n), replaces 40,000 passes.

BFS or DFS?

This is the question worth being deliberate about, because the earlier problems in this section all demanded breadth-first search and this one does not.

Breadth-first search expands in rings of equal distance, and that layered structure is what guarantees the first arrival at a cell is the shortest one. You need it exactly when the answer is a distance or a minimum number of steps.

Here the answer is neither. It is a yes-or-no about reachability. Whether a cell is reached in 3 steps or 30 makes no difference to the output, so the ordering guarantee that BFS pays for is worthless. Either traversal visits precisely the same set of cells and produces precisely the same answer.

Given the choice, depth-first search is usually the more comfortable fit: it is a few lines recursively, and it holds only the current route on the call stack rather than an entire frontier. The caveat is that the recursion depth can reach m × n on a long snaking path — 40,000 frames here, beyond Python's default limit — so an iterative version with an explicit stack, or BFS with a queue, is the safe choice at scale.

The decision rule to carry forward: asked for a shortest distance, use BFS; asked merely whether something is reachable, or asked to enumerate or count regions, either works and DFS is simpler.

The mechanism
python
def pacific_atlantic(heights):
    rows, cols = len(heights), len(heights[0])
    pacific, atlantic = set(), set()

    def climb(r, c, reached):
        reached.add((r, c))
        for nr, nc in ((r-1,c), (r+1,c), (r,c-1), (r,c+1)):
            if (0 <= nr < rows and 0 <= nc < cols
                    and (nr, nc) not in reached
                    and heights[nr][nc] >= heights[r][c]):    # uphill or level
                climb(nr, nc, reached)

    for c in range(cols):
        climb(0, c, pacific)                 # top edge touches the Pacific
        climb(rows - 1, c, atlantic)         # bottom edge touches the Atlantic
    for r in range(rows):
        climb(r, 0, pacific)                 # left edge touches the Pacific
        climb(r, cols - 1, atlantic)         # right edge touches the Atlantic

    return [[r, c] for r, c in pacific & atlantic]
Crucial Notethe comparison is heights[neighbour] >= heights[current] — greater than or equal. Two mistakes hide here. Writing a strict > would make flat regions impassable, and since the problem explicitly permits flow between equal heights, a plateau touching the coast would be wrongly excluded. And getting the direction backwards — comparing <= because the problem statement says water flows downhill — silently computes which cells the ocean can flow up from, which is meaningless. Say out loud which direction you are walking before writing the comparison.
Crucial Notethe two reached-sets must stay separate. Sharing one set would let a cell claimed by the Pacific traversal block the Atlantic traversal from ever visiting it, and the intersection would come out empty or wrong. Each ocean needs its own independent record.

Each cell is added to each set at most once and inspects four neighbours, so the work is O(m × n) time with O(m × n) memory for the two sets.

Worked example:heights = [[1,2,3],[8,9,4],[7,6,5]]

A spiral, ascending from the 1 in the top-left corner around to the 9 in the middle.

The Pacific climb starts from the top row — (0,0)=1, (0,1)=2, (0,2)=3 — and the left column — (0,0)=1, (1,0)=8, (2,0)=7.

- From (0,1)=2, the neighbour (1,1)=9 is higher, so it is claimed. From (0,2)=3 the neighbour (1,2)=4 is higher, claimed.
- From (1,0)=8, the neighbour (1,1)=9 is higher — already claimed. The neighbour (2,0)=7 is lower, so it is not climbable from here, but (2,0) is a left-column seed anyway.
- From (2,0)=7, the neighbour (2,1)=6 is lower and not reachable by climbing.
- From (1,2)=4, the neighbour (2,2)=5 is higher, claimed. From (2,2)=5, the neighbour (2,1)=6 is higher, claimed.

Pacific reaches everything except… let us list it: (0,0), (0,1), (0,2), (1,0), (2,0), (1,1), (1,2), (2,2), (2,1). That is the whole grid — the ascending spiral means every cell is climbable from the low corner.

The Atlantic climb starts from the bottom row — (2,0)=7, (2,1)=6, (2,2)=5 — and the right column — (0,2)=3, (1,2)=4, (2,2)=5.

- From (2,0)=7 the neighbour (1,0)=8 is higher, claimed. From (1,0)=8 the neighbour (1,1)=9 is higher, claimed.
- From (0,2)=3 the neighbour (0,1)=2 is lower — not climbable. So (0,1) is never reached, and neither is (0,0)=1 beyond it.
- Everything else is reached: (2,0), (2,1), (2,2), (1,2), (1,0), (1,1), (0,2).

The intersection drops (0,0) and (0,1), leaving (0,2), (1,0), (1,1), (1,2), (2,0), (2,1), (2,2) — seven cells, matching the expected output.

The instructive case is (0,0), holding 1. It sits directly on the Pacific coast, so water there certainly reaches the Pacific. But it is the lowest cell on the island, hemmed in by 2 and 8, so no water can ever descend from it toward the Atlantic. Being low helps water arrive; it is being high that lets water leave. That inversion is exactly what makes the reverse traversal the natural framing.

The transferable ideas

Three things carry beyond this problem.

Reverse the arrows. When a question asks which of many starting points can reach a few destinations, flip it and search backwards from the destinations. The reversal is valid whenever each individual step is reversible under a mirrored rule — here, "flows to a lower-or-equal neighbour" becomes "climbs to a higher-or-equal neighbour". Check that reversibility explicitly; on a graph with genuinely one-way edges you must build the reverse adjacency first, and on a rule that is not cleanly invertible the trick simply fails.

Multi-source seeding. Many origins acting at once go into one traversal together, not into many traversals run in sequence. Whether the question is a distance from the nearest source or plain reachability, one pass suffices.

Compute each condition separately, then combine. The instinct on a "both A and B" question is to track both conditions inside a single traversal, which gets tangled fast — the routes to the two oceans are unrelated and a single walk cannot represent both. Running two independent traversals and intersecting the results is simpler to write and simpler to argue correct. Whenever a problem demands two independent properties of the same object, computing them in separate passes is usually the cleaner design.

Train the reflex: when a grid problem asks which cells can reach X, and X is small while the candidate cells are many, do not search from the cells. Sit on X, invert the movement rule, and let the search come to you.

Interactive Strategy Visualization
Phase 1 — Pacific BFS
Dual Multi-Source BFS
1(0,0)
2(0,1)
2(0,2)
3(0,3)
3(1,0)
2(1,1)
3(1,2)
4(1,3)
2(2,0)
4(2,1)
5(2,2)
3(2,3)
Pacific
Atlantic
Both (Divide)
In Queue
Current
Queue (0)empty

Start: seed Pacific queue with left column and top row border cells.

STEP 1/49
O((M × N)²) Search Downhill From Every Cell
O(M × N) Two Reverse Multi-Source Traversals
O(M × N) Intersection Of Two Reach Sets