Talentd
FleetCode
Make and follow sheets.Sign in

Trapping Rain Water II solution and explanation

HardArrayBreadth-First SearchHeap (Priority Queue)Matrix7 min read64.0% acceptanceUpdated 9 Apr 2026
Trapping Rain Water II - Leetcode 407 - PythonNeetCodeIO · 15:19 · 30K viewsWatch on YouTube
9 more videos on this problem
  1. Trapping Rain Water II - Leetcode 407 - PythonNeetCodeIO · 15:19 · 30K views
  2. Trapping Rain Water II | Deep Dive Explanation | What | Why | How | Leetcode 407 | codestorywithMIKcodestorywithMIK · 59:17 · 26K views
  3. Trapping Rain Water II | Leetcode 407Techdose · 31:10 · 13K views
  4. LeetCode 407. Trapping Rain Water II | Hard | Algorithm Explained | C++Cherry Coding [IIT-G] · 15:54 · 12K views
  5. LeetCode 407. Trapping Rain Water II Explanation and Solutionhappygirlzt · 14:33 · 9K views
  6. 407. Trapping Rain Water II | BFS | Priority QueueAryan Mittal · 21:36 · 7K views
  7. 407. Trapping Rain Water II | leetcode daily challenge | shashcode | java | dsaShashCode · 22:30 · 3K views
  8. Trapping Rain Water II | LeetCode 407 | Complete Intuition ExplainedSanyam IIT Guwahati · 16:30 · 2K views
  9. 407. Trapping Rain Water II (Leetcode Hard)Programming Live with Larry · 1:13:18 · 1K views
  10. LeetCode 407. Trapping Rain Water II宰相小甘罗 · 5:28 · 298 views

The problem

Given an m x n integer matrix heightMap representing the height of each unit cell in a 2D elevation map, return the volume of water it can trap after raining.

Example 1:

Input: heightMap = [[1,4,3,1,3,2],[3,2,1,3,2,4],[2,3,3,2,3,1]]
Output: 4
Explanation: After the rain, water is trapped between the blocks.
We have two small ponds 1 and 3 units trapped.
The total volume of water trapped is 4.

Example 2:

Input: heightMap = [[3,3,3,3,3],[3,2,2,2,3],[3,2,1,2,3],[3,2,2,2,3],[3,3,3,3,3]]
Output: 10

Constraints:

  • m == heightMap.length
  • n == heightMap[i].length
  • 1 <= m, n <= 200
  • 0 <= heightMap[i][j] <= 2 * 104

Approach overview

Problem Overview: You are given a 2D grid where each cell represents elevation. After rainfall, water can be trapped between taller boundary cells. The task is to compute how many total units of water remain trapped after the system stabilizes.

Approach 1: Min-Heap Priority Queue + BFS (O(mn log(mn)) time, O(mn) space)

This problem behaves like filling water from the outside boundary inward. Start by pushing all boundary cells into a min-heap. The heap always expands from the lowest boundary height first. Pop the lowest cell, explore its four neighbors using a BFS-style traversal, and determine whether water can be trapped there. If a neighbor is lower than the current boundary height, the difference is trapped water. Push the neighbor back into the heap with an effective height of max(currentHeight, neighborHeight). This simulates raising the water level as the basin fills.

The key insight: the lowest boundary always determines the maximum water level of the region it expands into. Using a heap ensures cells are processed in increasing height order. Each cell is visited once and inserted into the heap at most once, giving O(mn log(mn)) time due to heap operations and O(mn) space for the visited matrix and heap. This approach combines ideas from Heap (Priority Queue), Breadth-First Search, and grid traversal.

Approach 2: Flood Fill Simulation (O(mn log(mn)) time, O(mn) space)

Another perspective treats the problem as a flood-fill from the outer boundary. Initialize the boundary as the starting frontier and gradually propagate inward. Instead of thinking about trapping water locally, maintain the current water level defined by the smallest boundary cell encountered. Each step expands to neighboring cells and updates the water level accordingly. If a cell is lower than the active water level, the difference contributes to trapped volume.

The flood fill behaves similarly to a Dijkstra-style expansion across the grid. The frontier must still be processed in increasing elevation order, so a priority queue is typically used internally. The algorithm marks cells as visited to prevent revisiting and keeps updating the effective boundary height as it expands inward. Complexity remains O(mn log(mn)) time with O(mn) auxiliary space.

This formulation helps when reasoning about water propagation in a matrix environment. It also clarifies why the outer boundary must be processed first: water can always escape through the lowest surrounding wall.

Recommended for interviews: The min-heap boundary expansion approach is what interviewers expect. It demonstrates understanding of priority queues, BFS-style grid traversal, and the key insight that the minimum boundary controls the water level. Explaining the flood-fill intuition helps justify the algorithm, but implementing the heap-based BFS shows stronger problem-solving skill.

Approach 1: Using a Min-Heap Priority Queue

This approach utilizes a min-heap (priority queue) to efficiently determine the minimum boundary height around a smaller section of the map. Initially, all the border points are pushed into the heap. We then process each cell in the heap by examining its neighbors. If the height of a neighboring cell is lower, it indicates that water can be trapped above it. The heap is utilized to ensure that the next cell to process is the one which determines the lowest possible trapped water boundary for its neighbors.

import heapq

def trapRainWater(heightMap):
    if not heightMap or not heightMap[0]:
        return 0

    m, n = len(heightMap), len(heightMap[0])
    visited = [[False] * n for _ in range(m)]
    heap = []
    
    # Push all the boundary cells into the heap
    for i in range(m):
        for j in range(n):
            if i == 0 or j == 0 or i == m - 1 or j == n - 1:
                heapq.heappush(heap, (heightMap[i][j], i, j))
                visited[i][j] = True

    directions = [(1, 0), (-1, 0), (0, 1), (0, -1)]
    trappedWater = 0

    while heap:
        height, x, y = heapq.heappop(heap)

        for dx, dy in directions:
            nx, ny = x + dx, y + dy

            if 0 <= nx < m and 0 <= ny < n and not visited[nx][ny]:
                trappedWater += max(0, height - heightMap[nx][ny])
                heapq.heappush(heap, (max(height, heightMap[nx][ny]), nx, ny))
                visited[nx][ny] = True

    return trappedWater
import java.util.PriorityQueue;

class Solution {
    private class Cell {
        int height, x, y;
        Cell(int h, int i, int j) { this.height = h; this.x = i; this.y = j; }
    }

    public int trapRainWater(int[][] heightMap) {
        if (heightMap == null || heightMap.length == 0 || heightMap[0].length == 0) return 0;

        int m = heightMap.length, n = heightMap[0].length;
        PriorityQueue<Cell> pq = new PriorityQueue<>((a, b) -> a.height - b.height);
        boolean[][] visited = new boolean[m][n];

        // Push all the boundary cells into the heap
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (i == 0 || j == 0 || i == m - 1 || j == n - 1) {
                    pq.add(new Cell(heightMap[i][j], i, j));
                    visited[i][j] = true;
                }
            }
        }

        int[][] directions = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
        int trappedWater = 0;

        while (!pq.isEmpty()) {
            Cell cell = pq.poll();

            for (int[] d : directions) {
                int nx = cell.x + d[0], ny = cell.y + d[1];

                if (nx < 0 || nx >= m || ny < 0 || ny >= n || visited[nx][ny]) continue;

                trappedWater += Math.max(0, cell.height - heightMap[nx][ny]);
                pq.add(new Cell(Math.max(cell.height, heightMap[nx][ny]), nx, ny));
                visited[nx][ny] = true;
            }
        }

        return trappedWater;
    }
}
#include <vector>
#include <queue>
#include <functional>

using namespace std;

class Solution {
public:
    struct Cell {
        int x, y, height;
        Cell(int height, int x, int y) : height(height), x(x), y(y) {}
        bool operator>(const Cell& other) const { return height > other.height; }
    };

    int trapRainWater(vector<vector<int>>& heightMap) {
        if (heightMap.empty()) return 0;

        int m = heightMap.size(), n = heightMap[0].size();
        priority_queue<Cell, vector<Cell>, greater<Cell>> pq;
        vector<vector<bool>> visited(m, vector<bool>(n, false));

        for (int i = 0; i < m; ++i) {
            for (int j = 0; j < n; ++j) {
                if (i == 0 || j == 0 || i == m - 1 || j == n - 1) {
                    pq.push(Cell(heightMap[i][j], i, j));
                    visited[i][j] = true;
                }
            }
        }

        int directions[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
        int trappedWater = 0;

        while (!pq.empty()) {
            Cell cell = pq.top(); pq.pop();

            for (auto& dir : directions) {
                int nx = cell.x + dir[0], ny = cell.y + dir[1];

                if (nx >= 0 && nx < m && ny >= 0 && ny < n && !visited[nx][ny]) {
                    trappedWater += max(0, cell.height - heightMap[nx][ny]);
                    pq.push(Cell(max(cell.height, heightMap[nx][ny]), nx, ny));
                    visited[nx][ny] = true;
                }
            }
        }

        return trappedWater;
    }
};
var trapRainWater = function(heightMap) {
    if (!heightMap || !heightMap.length || !heightMap[0].length) return 0;
    
    const m = heightMap.length, n = heightMap[0].length;
    const visited = Array.from({ length: m }, () => Array(n).fill(false));
    const heap = new MinPriorityQueue({ priority: x => x.height });

    for (let i = 0; i < m; i++) {
        for (let j = 0; j < n; j++) {
            if (i === 0 || j === 0 || i === m - 1 || j === n - 1) {
                heap.enqueue({ height: heightMap[i][j], x: i, y: j });
                visited[i][j] = true;
            }
        }
    }

    const directions = [[1, 0], [-1, 0], [0, 1], [0, -1]];
    let trappedWater = 0;

    while (!heap.isEmpty()) {
        const { element: { height, x, y } } = heap.dequeue();

        for (const [dx, dy] of directions) {
            const nx = x + dx, ny = y + dy;

            if (nx >= 0 && nx < m && ny >= 0 && ny < n && !visited[nx][ny]) {
                trappedWater += Math.max(0, height - heightMap[nx][ny]);
                heap.enqueue({ height: Math.max(height, heightMap[nx][ny]), x: nx, y: ny });
                visited[nx][ny] = true;
            }
        }
    }

    return trappedWater;
};
using System;
using System.Collections.Generic;

public class Solution {
    public class Cell : IComparable<Cell> {
        public int height, row, col;
        public Cell(int h, int r, int c) { height = h; row = r; col = c; }
        public int CompareTo(Cell other) => height.CompareTo(other.height);
    }

    public int TrapRainWater(int[][] heightMap) {
        if (heightMap == null || heightMap.Length == 0 || heightMap[0].Length == 0) return 0;

        int m = heightMap.Length, n = heightMap[0].Length;
        var pq = new SortedSet<Cell>();
        bool[,] visited = new bool[m, n];

        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (i == 0 || j == 0 || i == m - 1 || j == n - 1) {
                    pq.Add(new Cell(heightMap[i][j], i, j));
                    visited[i, j] = true;
                }
            }
        }

        int[][] directions = new int[][] {
            new int[] {1, 0}, new int[] {-1, 0},
            new int[] {0, 1}, new int[] {0, -1}
        };
        int trappedWater = 0;

        while (pq.Count > 0) {
            var cell = pq.Min;
            pq.Remove(cell);

            foreach (var dir in directions) {
                int nx = cell.row + dir[0], ny = cell.col + dir[1];

                if (nx >= 0 && nx < m && ny >= 0 && ny < n && !visited[nx, ny]) {
                    trappedWater += Math.Max(0, cell.height - heightMap[nx][ny]);
                    pq.Add(new Cell(Math.Max(cell.height, heightMap[nx][ny]), nx, ny));
                    visited[nx, ny] = true;
                }
            }
        }

        return trappedWater;
    }
}

The function first initializes a priority queue with all boundary cells, marking them as visited. Cells are processed from the queue in order of increasing height. For each cell, its neighbors are checked to determine if water can be trapped. If so, water is added, and the neighbor cell is pushed into the queue as potentially trapping more water.

Complexity

Time Complexity: O(m * n * log(m * n)), where m is the number of rows and n is the number of columns, due to priority queue operations.
Space Complexity: O(m * n) because of the auxiliary structures used for visited tracking and min-heap storage.

Try this approach in the editor

Approach 2: Flood Fill Technique

This approach employs a flood-fill technique starting from the boundary cells and leverages depth-first or breadth-first search to explore the inward cells. The idea is similar to how flood-fill works in graphics, where all connected cells are considered to be part of the same region and receive the same 'flood' level. This approach ensures that all accessible cells connected through any boundary are given a chance to receive water, effectively checking for possible traps.

from collections import deque

def trapRainWater(heightMap):
    if not heightMap or not heightMap[0]:
        return 0

    m, n = len(heightMap), len(heightMap[0])
    visited = [[False] * n for _ in range(m)]
    bfs_queue = deque()
    
    # Initialize BFS queue with border cells
    for i in range(m):
        for j in range(n):
            if i == 0 or j == 0 or i == m - 1 or j == n - 1:
                bfs_queue.append((i, j))
                visited[i][j] = True

    trappedWater = 0

    while bfs_queue:
        x, y = bfs_queue.popleft()

        for dx, dy in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
            nx, ny = x + dx, y + dy

            if 0 <= nx < m and 0 <= ny < n and not visited[nx][ny]:
                if heightMap[nx][ny] < heightMap[x][y]:
                    trappedWater += heightMap[x][y] - heightMap[nx][ny]
                    heightMap[nx][ny] = heightMap[x][y]  # Update the height to mark flooded level
                bfs_queue.append((nx, ny))
                visited[nx][ny] = True

    return trappedWater

This solution uses a BFS queue to perform the flood-fill operation on the height map. Each cell on the border is considered starting point, and its height is used to fill inward cells until no more water can be trapped. The design tracks visited cells and updates levels using the fill technique to maintain overflow boundaries.

Complexity

Time Complexity: O(m * n), where m is the number of rows and n is the number of columns, given the potential need to inspect each cell.
Space Complexity: O(m * n) to handle the visited array and BFS queue.

Try this approach in the editor

Approach 3: Priority Queue (Min Heap)

This is a variant of the trapping rain water problem. Since the heights on the matrix boundaries are fixed, we can add these boundary heights to a priority queue. Then, we repeatedly take out the minimum height from the priority queue and compare it with the heights of its four adjacent cells. If an adjacent cell's height is less than the current height, we can trap water there. The volume of trapped water is the difference between the current height and the adjacent height. We then add the larger height back to the priority queue and repeat this process until the priority queue is empty.

The time complexity is O(m times n times log (m times n)), and the space complexity is O(m times n), where m and n are the number of rows and columns in the matrix, respectively.

function trapRainWater(heightMap: number[][]): number {
    const m = heightMap.length;
    const n = heightMap[0].length;
    const vis: boolean[][] = Array.from({ length: m }, () => new Array(n).fill(false));
    const pq = new PriorityQueue<number[]>((a, b) => a[0] - b[0]);
    for (let i = 0; i < m; ++i) {
        for (let j = 0; j < n; ++j) {
            if (i === 0 || i === m - 1 || j === 0 || j === n - 1) {
                pq.enqueue([heightMap[i][j], i, j]);
                vis[i][j] = true;
            }
        }
    }

    let ans = 0;
    const dirs = [-1, 0, 1, 0, -1];
    while (!pq.isEmpty()) {
        const [h, i, j] = pq.dequeue();
        for (let k = 0; k < 4; ++k) {
            const x = i + dirs[k];
            const y = j + dirs[k + 1];
            if (x >= 0 && x < m && y >= 0 && y < n && !vis[x][y]) {
                ans += Math.max(0, h - heightMap[x][y]);
                vis[x][y] = true;
                pq.enqueue([Math.max(h, heightMap[x][y]), x, y]);
            }
        }
    }
    return ans;
}
func trapRainWater(heightMap [][]int) (ans int) {
	m, n := len(heightMap), len(heightMap[0])
	pq := hp{}
	vis := make([][]bool, m)
	for i, row := range heightMap {
		vis[i] = make([]bool, n)
		for j, v := range row {
			if i == 0 || i == m-1 || j == 0 || j == n-1 {
				heap.Push(&pq, tuple{v, i, j})
				vis[i][j] = true
			}
		}
	}
	dirs := []int{-1, 0, 1, 0, -1}
	for len(pq) > 0 {
		p := heap.Pop(&pq).(tuple)
		for k := 0; k < 4; k++ {
			x, y := p.i+dirs[k], p.j+dirs[k+1]
			if x >= 0 && x < m && y >= 0 && y < n && !vis[x][y] {
				ans += max(0, p.v-heightMap[x][y])
				vis[x][y] = true
				heap.Push(&pq, tuple{max(p.v, heightMap[x][y]), x, y})
			}
		}
	}
	return
}

type tuple struct{ v, i, j int }
type hp []tuple

func (h hp) Len() int           { return len(h) }
func (h hp) Less(i, j int) bool { return h[i].v < h[j].v }
func (h hp) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }
func (h *hp) Push(v any)        { *h = append(*h, v.(tuple)) }
func (h *hp) Pop() any          { a := *h; v := a[len(a)-1]; *h = a[:len(a)-1]; return v }
Try this approach in the editor

Complexity comparison

ApproachComplexity
Using a Min-Heap Priority Queue

Time Complexity: O(m * n * log(m * n)), where m is the number of rows and n is the number of columns, due to priority queue operations.
Space Complexity: O(m * n) because of the auxiliary structures used for visited tracking and min-heap storage.

Flood Fill Technique

Time Complexity: O(m * n), where m is the number of rows and n is the number of columns, given the potential need to inspect each cell.
Space Complexity: O(m * n) to handle the visited array and BFS queue.

Priority Queue (Min Heap)–

Complexity analysis

ApproachTimeSpaceWhen to Use
Min-Heap Priority Queue + BFSO(mn log(mn))O(mn)Standard optimal solution for 2D trapping rainwater problems. Expected in interviews.
Flood Fill Boundary ExpansionO(mn log(mn))O(mn)Useful for reasoning about water propagation across a grid or explaining the heap solution.

Frequently asked questions

Is Trapping Rain Water II easy or hard?

Trapping Rain Water II is classified as Hard on LeetCode. The difficulty comes from extending the 1D trapping rainwater concept into a 2D grid and recognizing that the solution requires a priority queue with BFS-style expansion.

How to solve Trapping Rain Water II in O(n)?

A true O(n) solution is not known for the 2D version of the problem. Unlike the 1D trapping rain water problem, the grid requires maintaining a global boundary order. The most efficient practical algorithm uses a min-heap and runs in O(mn log(mn)).

What is the best approach for Trapping Rain Water II?

The best approach uses a min-heap priority queue combined with BFS. All boundary cells are pushed into a heap, and the algorithm expands inward from the lowest boundary first. This ensures the current minimum wall height always determines how much water can be trapped. The time complexity is O(mn log(mn)) with O(mn) space.

What data structure is used in Trapping Rain Water II?

The core data structure is a min-heap (priority queue). It always processes the lowest boundary cell first, which determines the current water level. A visited matrix and BFS-style neighbor traversal are also used to explore the grid safely.

What is the time complexity of Trapping Rain Water II?

The optimal solution runs in O(mn log(mn)) time where m and n are grid dimensions. Each cell is processed once, but heap insertions and removals cost O(log(mn)). Space complexity is O(mn) for the visited matrix and priority queue.

Trapping Rain Water II Python or Java solution approach?

Python solutions typically use heapq with a visited matrix and directional traversal. Java implementations use PriorityQueue with a custom comparator for cell heights. Both follow the same boundary-expansion algorithm with O(mn log(mn)) complexity.

Is Trapping Rain Water II asked at Google or Amazon interviews?

Trapping Rain Water II appears in advanced interview rounds at companies like Google, Amazon, and Meta because it tests priority queues, BFS traversal, and spatial reasoning on grids. Candidates must combine multiple concepts rather than applying a single pattern.

Comments

Now write it yourselfClose this page and solve Trapping Rain Water II in the editor, without looking.
Practise Trapping Rain Water II