Pacific Atlantic Water Flow
Problem
There is an m x n rectangular island that borders both the Pacific Ocean and Atlantic Ocean. The Pacific Ocean touches the island’s left and top edges, and the Atlantic Ocean touches the island’s right and bottom edges.
The island is partitioned into a grid of square cells. You are given an m x n integer matrix heights where heights[r][c] represents the height above sea level of the cell at coordinate (r, c).
The island receives a lot of rain, and the rain water can flow to neighboring cells directly north, south, east, and west if the neighboring cell’s height is less than or equal to the current cell’s height. Water can flow from any cell adjacent to an ocean into the ocean.
Return a 2D list of grid coordinates result where result[i] = [ri, ci] denotes that rain water can flow from cell (ri, ci) to both the Pacific and Atlantic oceans.
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]]
Explanation: The following cells can flow to the Pacific and Atlantic oceans, as shown below:
[0,4]: [0,4] -> Pacific Ocean
[0,4] -> Atlantic Ocean
[1,3]: [1,3] -> [0,3] -> Pacific Ocean
[1,3] -> [1,4] -> Atlantic Ocean
[1,4]: [1,4] -> [1,3] -> [0,3] -> Pacific Ocean
[1,4] -> Atlantic Ocean
[2,2]: [2,2] -> [1,2] -> [0,2] -> Pacific Ocean
[2,2] -> [2,3] -> [2,4] -> Atlantic Ocean
[3,0]: [3,0] -> Pacific Ocean
[3,0] -> [4,0] -> Atlantic Ocean
[3,1]: [3,1] -> [3,0] -> Pacific Ocean
[3,1] -> [4,1] -> Atlantic Ocean
[4,0]: [4,0] -> Pacific Ocean
[4,0] -> Atlantic Ocean
Note that there are other possible paths for these cells to flow to the Pacific and Atlantic oceans.
Brainstorming
so for each cell we have to check the possibility of wether the water can travel travel top or left and bottom or right , if both are true then we can add that cell row and column to our results array , it is kind of similar to [[Longest increasing path in matrix]] in this sense .
but how do we check it ? we already have the condition for it . Each cell has a number and we can only move in that direction if the number in our next step is smaller than this. A DFS at each cell can work here but we have to be smart with our conditions
so we need to make sure that we move top left so i think we can just return True or false if we cannot move in either of these direction then we return False for Pacific
same we can do for bottom and right , we can just return True or False if we cannot move in either of these directions then we return for atlantic. If we can do both then we can just add the current cell to our final list
below is code structure
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]]
M = len(heights)
N = len(heights[0])
final_list = []
def dfs_helper(r,c,curr_height):
can_reach_atlantic = False
can_reach_pacific = False
# base case for boundaries
if r < 0 or c < 0 or r >= M or c >= N:
return False
# base case for success if we reach atlantic
if r == M -1 or c == N -1 :
return True
# base case for success if we reach pacific
if r == 0 or c == 0:
return True
if heights[r][c] <= curr_height:
can_reach_atlantic = (dfs_helper(r+1,c) or dfs_helper(r,c+1))
can_reach_pacific = (dfs_helper(r-1,c) or dfs_helper(r,c-1))
else:
return False
return can_reach_atlantic and can_reach_pacific
for i in range(M):
for j in range(N):
can_reach_pacific_and_atlantic = dfs_helper(r,c,heights[i][j]):
if can_reach_pacific_and_atlantic:
final_list.append([i,j])
- 🍅 (pomodoro::WORK) (duration:: 20m) (begin:: 2025-12-19 19:32) - (end:: 2025-12-19 19:52)
Optimal solution
we need to first fix the brute force
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]]
M = len(heights)
N = len(heights[0])
final_list = []
def dfs_helper(r,c,prev_height,visited):
# base case for boundaries
if r < 0 or c < 0 or r >= M or c >= N:
return (False,False)
# base case for height condition failure
if heights[r][c] > prev_height:
return (False, False)
visited.update([r,c])
can_reach_atlantic = False
can_reach_pacific = False
# if we reach atlantic
if r == M -1 or c == N -1 :
can_reach_atlantic = True
# if we reach pacific
if r == 0 or c == 0:
can_reach_pacific = True
pacific_res_down, atlantic_res_down = dfs_helper(r, c+1, heights[r][c], visited)
pacific_res_up , atlantic_res_up = dfs_helper(r, c-1, heights[r][c], visited)
pacific_res_left , atlantic_res_left = dfs_helper(r-1, c, heights[r][c], visited)
pacific_res_right , atlantic_res_right = dfs_helper(r+1, c, heights[r][c], visited)
can_reach_atlantic = can_reach_atlantic or atlantic_res_right or atlantic_res_left or atlantic_res_up or atlantic_res_down
can_reach_pacific = can_reach_pacific or pacific_res_right or pacific_res_left or pacific_res_up or pacific_res_down
visited.remove([r,c])
return can_reach_atlantic and can_reach_pacific
for i in range(M):
for j in range(N):
visited = set()
can_reach_pacific, can_reach_atlantic = dfs_helper(r,c,heights[i][j],visted):
if can_reach_pacific and can_reach_atlantic:
final_list.append([i,j])
print(final_list)
This is a valiant effort to fix the brute-
force approach. You have correctly identified almost all the complex pieces: returning a tuple, handling prev_height, and needing a visited set.
However, the implementation has some critical bugs that make it very difficult to fix. This is a perfect example of how a seemingly simple brute-force approach can become a “tar pit” of complexity. This is an important lesson in itself!
Let’s quickly point out the issues to see why the “flipped” approach is so much cleaner.
The visited set is broken:
visited.update(
[r,c]): This will add the numbers r and c to the set, not the coordinate tuple (r, c). You would need visited.add((r, c)).The bigger problem: You’re passing the same visited set down to all four recursive calls. The “down” call will add nodes to visited, and then the “up” call will see those nodes as already visited and fail incorrectly. You would need to pass a copy of the visited set to each recursive call, which is very inefficient.
visited.remove(
[r,c]) will also fail because[r,c]is a list, not the tuple (r,c) you would have added.
The Final return:
return can_reach_atlantic and can_reach_pacific
This still doesn’t work. A call to dfs_helper(r,c) will return True only if that specific subpath can reach both oceans, which is almost never true. You need the final and to be in the main for loop, not the helper. The helper needs to report everything it found. Your return (can_reach_pacific, can_reach_atlantic) was closer.
Let’s stop trying to fix this. The complexity is a clear sign that this is the wrong path. Your initial intuition to separate the problem was right, but this implementation path is a dead end.
Optimal The “Self-Nudge” - Let’s Flip the Problem Upside Down.
Instead of asking, “Can this cell flow out to the oceans?”
Let’s ask the opposite question: “Which cells can the ocean water flow into?”
This approach is vastly simpler to implement correctly.
Recap:
- Create a pacific_reachable set.
- Start DFS from all cells touching the Pacific to find every cell that can flow to them. Add these to pacific_reachable.
- Create an atlantic_reachable set.
- Start DFS from all cells touching the Atlantic to find every cell that can flow to them. Add these to atlantic_reachable.
- The answer is the intersection of these two sets.
Let’s Design the NEW dfs Helper for this approach.
dfs(r, c, visited_set, prev_height)
- visited_set: The specific set for the ocean we’re currently exploring (e.g., pacific_reachable).
- prev_height: The height of the cell we just came from. The rule for flowing “inland” is heights
[r][c]>= prev_height.
Logic for dfs(r, c, visited_set, prev_height):
Base Cases (Failure/Stop conditions):
- Out of bounds? (r < 0, etc.) -> Stop.
- Current cell already in visited_set? -> Stop (we’ve already processed this path).
- heights
[r][c]< prev_height? -> Can’t flow “uphill” from the ocean. Stop.
If none of the base cases are met, this is a valid cell:
“Visit”: Add (r, c) to the visited_set.
Explore: Make four recursive calls to neighbors. The new prev_height for these calls will be the height of the current cell, heights
[r][c].- dfs(r+1, c, visited_set, heights
[r][c]) - dfs(r-1, c, visited_set, heights
[r][c]) - … etc.
- dfs(r+1, c, visited_set, heights
There is no return value needed other than None. The function’s only job is to populate the visited_set. There’s also no explicit “undo” because we want the visited_set to be shared for the entire duration of one ocean’s search.
(Prompt):
- Write the code for this new, simpler dfs helper.
- Write the main for loops that will kick off this dfs for all the Pacific border cells.
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]]
M = len(heights) # rows
N = len(heights[0]) # columns
pacific_reachable = set()
atlantic_reachable = set()
final_list = []
# recursive helper
def dfs_helper(r,c,visited,prev_height):
if r < 0 or c < 0 or r >= M or c >=N:
return
if (r,c) in visited:
return
if heights[r][c] < prev_height:
return
visited.add((r,c))
dfs_helper(r+1,c, visited, heights[r][c])
dfs_helper(r-1,c, visited, heights[r][c])
dfs_helper(r,c+1, visited, heights[r][c])
dfs_helper(r,c-1, visited, heights[r][c])
# base cases top row and left column is pacific reachable
for j in range(N): # top row
dfs_helper(0,j,pacific_reachable, heights[0][j])
for i in range(M): # left column
dfs_helper(i,0,pacific_reachable, heights[i][0])
# base cases for bottom row and right column is atlantic reachable
for j in range(N): # bottom row
dfs_helper(M-1, j, atlantic_reachable, heights[M-1][j])
for i in range(M): # right column
dfs_helper(i, N-1, atlantic_reachable, heights[i][N-1])
final_set = pacific_reachable & atlantic_reachable
# final list
for i in final_set:
final_list.append([i[0], i[1]])
print(final_list)
- 🍅 (pomodoro::WORK) (duration:: 30m) (begin:: 2025-12-19 20:20) - (end:: 2025-12-19 20:50)
- 🥤 (pomodoro::BREAK) (duration:: 15m) (begin:: 2025-12-19 20:52) - (end:: 2025-12-19 21:07)