Problem Overview
LeetCode 417 Pacific Atlantic Water Flow gives you an m x n integer matrix heights representing an island. The Pacific ocean touches the top and left borders of the island; the Atlantic ocean touches the bottom and right borders. Rain water can flow from a cell to any of its four neighbors if the neighbor's height is less than or equal to the current cell's height.
Your task is to return a list of all grid coordinates where rain water can flow to both the Pacific ocean and the Atlantic ocean. A cell can reach an ocean if there exists a path of non-increasing heights from that cell to any border touching that ocean.
This is a grid reachability problem with a directional constraint. Naively, you could try flooding from each cell toward both oceans, but that leads to O(m*n * m*n) worst-case time. The key insight is to invert the direction of flow.
- m == heights.length, n == heights[r].length; 1 ≤ m, n ≤ 200
- 0 ≤ heights[r][c] ≤ 10^5
- Pacific ocean borders: top row (row 0) and left column (col 0)
- Atlantic ocean borders: bottom row (row m-1) and right column (col n-1)
- Water flows to neighbors with height ≤ current cell height
- Return all [r, c] pairs where water can reach BOTH oceans
Forward vs Reverse Thinking
The forward approach asks: from each cell, can water reach the Pacific? Can it reach the Atlantic? For each cell you run a DFS/BFS following non-increasing paths until hitting a border or dead end. With m*n cells each potentially traversing the entire grid, this is O((m*n)^2) in the worst case — too slow for 200x200 grids.
The reverse approach flips the direction of flow. Instead of asking "can water flow down from cell X to the ocean?", ask "what cells can the ocean reach by flowing uphill?" Start BFS/DFS from all Pacific border cells simultaneously, moving to neighbors with height greater than or equal to the current cell. Every cell visited is reachable from the Pacific. Do the same for Atlantic borders. The intersection of the two visited sets is the answer.
This transforms two separate O((m*n)^2) searches into two O(m*n) multi-source BFS/DFS passes — a dramatic improvement that fits easily within the 200x200 constraint.
Reverse the Flow Direction
Start from the ocean and go uphill — this turns an O(n^4) problem into O(n^2). Instead of checking if water can flow down from each cell to the ocean, check which cells the ocean can reach by flowing uphill. The condition flips from height[neighbor] <= height[current] to height[neighbor] >= height[current].
Reverse DFS/BFS from Pacific
Initialize a visited set pacific_reachable. Add all cells on the top row (row 0, all columns) and left column (all rows, col 0) as starting points — these border cells can trivially drain to the Pacific. Push all of them into the BFS queue (or use them as DFS starting nodes) simultaneously.
From each border cell, expand to its four neighbors. A neighbor is reachable from the Pacific if it has not been visited yet and its height is greater than or equal to the current cell's height. This uphill condition is the reverse of normal water flow. Mark each newly reachable cell and continue expanding.
After the traversal completes, pacific_reachable contains every cell whose water can drain to the Pacific ocean. The key property: if cell A can flow up to cell B (height[B] >= height[A]) in reverse BFS, then in the forward direction cell B's water can flow down to cell A and eventually reach the Pacific.
- 1Initialize pacific_reachable as an empty set
- 2Seed BFS queue with all cells in row 0 (top border) and col 0 (left border)
- 3Mark all seeded cells as visited in pacific_reachable
- 4BFS: for each cell, check 4 neighbors (up, down, left, right)
- 5Visit neighbor if not yet visited AND heights[neighbor] >= heights[current]
- 6Add visited neighbors to queue and pacific_reachable
Reverse DFS/BFS from Atlantic
Repeat the same process for the Atlantic ocean. Initialize atlantic_reachable and seed it with all cells on the bottom row (row m-1) and right column (col n-1). These cells border the Atlantic and can trivially drain to it. Run the same uphill BFS/DFS from these starting points.
After this second traversal, atlantic_reachable contains every cell whose water can drain to the Atlantic. The final answer is all cells that appear in both pacific_reachable and atlantic_reachable — the intersection of the two sets.
Collect the result by iterating over all cells and checking membership in both sets. Since set lookup is O(1), the final collection step is O(m*n). Total time complexity is O(m*n) and space complexity is O(m*n) for the two visited sets.
Two Visited Sets, Not One
Using two separate visited sets and intersecting them is cleaner than trying to track both oceans in a single traversal. A single-pass approach would need bitmask flags per cell and complex logic to avoid interference between the two ocean expansions. Two independent BFS passes are simpler, equally fast, and much easier to reason about correctly.
BFS vs DFS Trade-offs
Both BFS and DFS produce correct results for this problem. BFS uses a deque and processes cells level by level from the border inward. DFS uses either explicit recursion or a stack. The asymptotic complexity is identical: O(m*n) time and O(m*n) space for both approaches.
BFS tends to use more memory during execution because the queue can hold up to O(m*n) cells at once, while DFS only holds the current path on the call stack. However, recursive DFS risks hitting Python's recursion limit on large 200x200 grids — use iterative DFS or BFS if recursion depth is a concern.
In interviews, BFS is often preferred for grid problems because it naturally models the multi-source "flood from the border" pattern. DFS produces slightly shorter code in Python and is easier to write from memory. Choose based on your comfort level; the grader accepts either.
- 1BFS: use collections.deque, append border cells, popleft in loop
- 2DFS iterative: use list as stack, append border cells, pop in loop
- 3DFS recursive: helper(r, c, visited) with base case for bounds and visited check
- 4Both: check heights[nr][nc] >= heights[r][c] for the uphill condition
- 5Both: mark cell visited before adding to queue/stack to avoid duplicates
Code Walkthrough Python and Java
In Python: create two sets pacific and atlantic. Define a bfs(starts, reachable) helper that takes a list of border cell coordinates and fills the reachable set. Call bfs with Pacific border cells, then Atlantic border cells. Return [[r,c] for r in range(m) for c in range(n) if (r,c) in pacific and (r,c) in atlantic].
In Java: use two boolean[][] arrays pacific and atlantic instead of sets. Define a void bfs(int[][] heights, boolean[][] visited, Deque<int[]> queue) method. Seed the queues with border cells, run BFS, then iterate all cells to collect those with both arrays marked true.
Time complexity is O(m*n) — each cell is visited at most once per BFS pass and there are two passes. Space complexity is O(m*n) for the visited sets/arrays and the BFS queue. The final result collection is also O(m*n).
Uphill Flow Condition is Reversed
The flow condition is reversed: move to cells with height >= current height, not <=. In the forward direction water flows downhill (to cells with height <= current). In the reverse direction from the ocean going inward, we flow uphill (to cells with height >= current). Getting this backwards is the most common bug — double-check that you use >= not <=.