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],[0,1]]Constraints:
1 <= heights.length, heights[r].length <= 1000 <= heights[r][c] <= 1000
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.
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.
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?
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.
Before attempting this problem, you should be comfortable with:
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):
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.
(r, c):pacific = false, atlantic = false.DFS(r, c, prevVal = +∞).DFS(r, c, prevVal):r < 0 or c < 0: set pacific = true, return.r == ROWS or c == COLS: set atlantic = true, return.heights[r][c] > prevVal: return (can't flow uphill).prevVal = currentHeight.DFS both flags are true, add (r, c) to 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)]
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 resWhere is the number of rows and is the number of columns.
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:
pacatlAnswer = cells that are in both sets.
pac, atl.DFS rule (reverse flow):(r, c), you may go to a neighbor (nr, nc) only ifheights[nr][nc] >= heights[r][c] (uphill or same).DFS from every Pacific border cell, fill pac.DFS from every Atlantic border cell, fill atl.pac and atl, add it to 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 resWhere is the number of rows and is the number of columns.
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:
heights[nr][nc] >= heights[r][c]So:
pac[r][c] = Trueatl[r][c] = TrueTrue in both are the answer.pac and atl (same size as heights), all false.pacificSources: all cells on top row + left columnatlanticSources: all cells on bottom row + right columnBFS(sources, oceanGrid):(r, c), mark oceanGrid[r][c] = true(nr, nc) in 4 directions:oceanGrid AND heights[nr][nc] >= heights[r][c],(nr, nc) into queue.BFS for Pacific → fill pac.BFS for Atlantic → fill atl.pac[r][c] and atl[r][c] are both true, add [r, c] to 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 resWhere is the number of rows and is the number of columns.
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.
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.
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.