417. Pacific Atlantic Water Flow - Explanation
Description
You are given a rectangular island heights where heights[r][c] represents the height above sea level of the cell at coordinate (r, c).
The islands borders the Pacific Ocean from the top and left sides, and borders the Atlantic Ocean from the bottom and right sides.
Water can flow in four directions (up, down, left, or right) from a cell to a neighboring cell with height equal or lower. Water can also flow into the ocean from cells adjacent to the ocean.
Find all cells where water can flow from that cell to both the Pacific and Atlantic oceans. Return it as a 2D list where each element is a list [r, c] representing the row and column of the cell. You may return the answer in any order.
Example 1:
Input: heights = [
[4,2,7,3,4],
[7,4,6,4,7],
[6,3,5,3,6]
]
Output: [[0,2],[0,4],[1,0],[1,1],[1,2],[1,3],[1,4],[2,0]]Example 2:
Input: heights = [[1],[1]]
Output: [[0,0],[1,0]]Constraints:
1 <= heights.length, heights[r].length <= 1000 <= heights[r][c] <= 1000
Topics
Recommended Time & Space Complexity
You should aim for a solution with O(m * n) time and O(m * n) space, where m is the number of rows and n is the number of columns in the grid.
Hint 1
A brute force solution would be to traverse each cell in the grid and run a BFS from each cell to check if it can reach both oceans. This would result in an O((m * n)^2) solution. Can you think of a better way? Maybe you should consider a reverse way of traversing.
Hint 2
We can use the Depth First Search (DFS) algorithm starting from the border cells of the grid. However, we reverse the condition such that the next visiting cell should have a height greater than or equal to the current cell. For the top and left borders connected to the Pacific Ocean, we use a hash set called pacific and run a DFS from each of these cells, visiting nodes recursively. Similarly, for the bottom and right borders connected to the Atlantic Ocean, we use a hash set called atlantic and run a DFS. The required coordinates are the cells that exist in both the pacific and atlantic sets. How do you implement this?
Hint 3
We perform DFS from the border cells, using their respective hash sets. During the DFS, we recursively visit the neighbouring cells that are unvisited and have height greater than or equal to the current cell's height and add the current cell's coordinates to the corresponding hash set. Once the DFS completes, we traverse the grid and check if a cell exists in both the hash sets. If so, we add that cell to the result list.
Prerequisites
Before attempting this problem, you should be comfortable with:
- Depth First Search (DFS) - Recursive traversal to explore connected components and paths in grids
- Breadth First Search (BFS) - Level-by-level traversal as an alternative approach for grid exploration
- 2D Matrix/Grid Traversal - Moving in four directions and tracking visited cells
- Multi-source Search - Starting BFS/DFS from multiple boundary cells simultaneously
1. Brute Force (Backtracking)
Intuition
For each cell, we try to see where water can flow if it starts there.
Rule: water can flow from a cell to a neighbor only if the neighbor’s height is <= current height (downhill or flat).
So for every (r, c):
- Run DFS exploring all paths that keep going to same or lower heights.
- If during DFS we ever step out of the grid:
- Out of top/left boundary ⇒ it can reach the Pacific.
- Out of bottom/right boundary ⇒ it can reach the Atlantic.
- If both oceans are reachable, include (r, c) in the answer.
To avoid infinite loops, we temporarily mark the current cell as visited (here by setting it to inf) while exploring, then restore it after backtracking.
Algorithm
- For each cell
(r, c):- Set
pacific = false,atlantic = false. DFS(r, c, prevVal = +∞).
- Set
DFS(r, c, prevVal):- If
r < 0orc < 0: setpacific = true, return. - If
r == ROWSorc == COLS: setatlantic = true, return. - If
heights[r][c] > prevVal: return (can't flow uphill). - Mark cell as visited (temporary), explore 4 neighbors with
prevVal = currentHeight. - If both oceans found, you can stop early.
- Restore the cell value (backtrack).
- If
- If after
DFSboth flags aretrue, add(r, c)to result. - Return result list.
class Solution:
def pacificAtlantic(self, heights: List[List[int]]) -> List[List[int]]:
ROWS, COLS = len(heights), len(heights[0])
directions = [(1, 0), (-1, 0), (0, 1), (0, -1)]
pacific = atlantic = False
def dfs(r, c, prevVal):
nonlocal pacific, atlantic
if r < 0 or c < 0:
pacific = True
return
if r >= ROWS or c >= COLS:
atlantic = True
return
if heights[r][c] > prevVal:
return
tmp = heights[r][c]
heights[r][c] = float('inf')
for dx, dy in directions:
dfs(r + dx, c + dy, tmp)
if pacific and atlantic:
break
heights[r][c] = tmp
res = []
for r in range(ROWS):
for c in range(COLS):
pacific = False
atlantic = False
dfs(r, c, float('inf'))
if pacific and atlantic:
res.append([r, c])
return resTime & Space Complexity
- Time complexity:
- Space complexity:
Where is the number of rows and is the number of columns.
2. Depth First Search
Intuition
Instead of starting DFS from every cell (slow), we reverse the thinking:
A cell can reach an ocean if water can flow from that cell to the ocean (downhill/flat).
Reverse it: start from the ocean borders and move uphill/flat (to neighbors with height >= current).
If you can climb from the ocean to a cell, then that cell can flow down to that ocean.
So we do 2 DFS runs:
- From all Pacific border cells (top row + left column) → mark all reachable cells in
pac - From all Atlantic border cells (bottom row + right column) → mark all reachable cells in
atl
Answer = cells that are in both sets.
Algorithm
- Create two visited sets:
pac,atl. DFSrule (reverse flow):- From cell
(r, c), you may go to a neighbor(nr, nc)only ifheights[nr][nc] >= heights[r][c](uphill or same).
- From cell
- Run
DFSfrom every Pacific border cell, fillpac. - Run
DFSfrom every Atlantic border cell, fillatl. - For every cell in the grid, if it's in both
pacandatl, add it to result. - Return result.
class Solution:
def pacificAtlantic(self, heights: List[List[int]]) -> List[List[int]]:
ROWS, COLS = len(heights), len(heights[0])
pac, atl = set(), set()
def dfs(r, c, visit, prevHeight):
if ((r, c) in visit or
r < 0 or c < 0 or
r == ROWS or c == COLS or
heights[r][c] < prevHeight
):
return
visit.add((r, c))
dfs(r + 1, c, visit, heights[r][c])
dfs(r - 1, c, visit, heights[r][c])
dfs(r, c + 1, visit, heights[r][c])
dfs(r, c - 1, visit, heights[r][c])
for c in range(COLS):
dfs(0, c, pac, heights[0][c])
dfs(ROWS - 1, c, atl, heights[ROWS - 1][c])
for r in range(ROWS):
dfs(r, 0, pac, heights[r][0])
dfs(r, COLS - 1, atl, heights[r][COLS - 1])
res = []
for r in range(ROWS):
for c in range(COLS):
if (r, c) in pac and (r, c) in atl:
res.append([r, c])
return resTime & Space Complexity
- Time complexity:
- Space complexity:
Where is the number of rows and is the number of columns.
3. Breadth First Search
Intuition
Do the same “reverse flow” idea, but with BFS.
A cell can flow to an ocean if you can start from the ocean border and move backwards into the grid using the rule:
- from (r, c) you can go to neighbor (nr, nc) if
heights[nr][nc] >= heights[r][c]
(because in the real direction water would flow from higher/equal down to (r, c)).
So:
- Multi-source BFS from all Pacific border cells marks
pac[r][c] = True - Multi-source BFS from all Atlantic border cells marks
atl[r][c] = True
Cells that areTruein both are the answer.
Algorithm
- Create two boolean grids
pacandatl(same size asheights), allfalse. - Build two source lists:
pacificSources: all cells on top row + left columnatlanticSources: all cells on bottom row + right column
- Run
BFS(sources, oceanGrid):- Initialize queue with all sources.
- While queue not empty:
- pop
(r, c), markoceanGrid[r][c] = true - for each neighbor
(nr, nc)in 4 directions:- if inside grid AND not visited in
oceanGridANDheights[nr][nc] >= heights[r][c],
push(nr, nc)into queue.
- if inside grid AND not visited in
- pop
- Run
BFSfor Pacific → fillpac. - Run
BFSfor Atlantic → fillatl. - Iterate all cells; if
pac[r][c]andatl[r][c]are bothtrue, add[r, c]to result. - Return result.
class Solution:
def pacificAtlantic(self, heights: List[List[int]]) -> List[List[int]]:
ROWS, COLS = len(heights), len(heights[0])
directions = [(1, 0), (-1, 0), (0, 1), (0, -1)]
pac = [[False] * COLS for _ in range(ROWS)]
atl = [[False] * COLS for _ in range(ROWS)]
def bfs(source, ocean):
q = deque(source)
while q:
r, c = q.popleft()
ocean[r][c] = True
for dr, dc in directions:
nr, nc = r + dr, c + dc
if (0 <= nr < ROWS and 0 <= nc < COLS and
not ocean[nr][nc] and
heights[nr][nc] >= heights[r][c]
):
q.append((nr, nc))
pacific = []
atlantic = []
for c in range(COLS):
pacific.append((0, c))
atlantic.append((ROWS - 1, c))
for r in range(ROWS):
pacific.append((r, 0))
atlantic.append((r, COLS - 1))
bfs(pacific, pac)
bfs(atlantic, atl)
res = []
for r in range(ROWS):
for c in range(COLS):
if pac[r][c] and atl[r][c]:
res.append([r, c])
return resTime & Space Complexity
- Time complexity:
- Space complexity:
Where is the number of rows and is the number of columns.
Common Pitfalls
Using the Wrong Flow Direction Comparison
When starting from ocean borders and moving inward, the comparison must check if the neighbor height is greater than or equal to the current height (water flows uphill in reverse). Using the normal downhill comparison (neighbor <= current) gives incorrect reachability since the logic is reversed.
Running Separate DFS/BFS for Each Cell
A brute force approach that runs a full traversal from every cell to check ocean reachability leads to O((m*n)^2) time complexity or worse. The efficient approach is to run only two traversals total: one multi-source search from all Pacific border cells and one from all Atlantic border cells.
Not Properly Handling Edge and Corner Cells
Edge cells border one ocean while corner cells border both oceans. A common bug is forgetting that cells on the top row and left column touch the Pacific, while cells on the bottom row and right column touch the Atlantic. Missing any border initialization causes incomplete reachability sets.
Sign in to join the discussion