Trapping Rain Water II

LeetCode problem 407. Trapping Rain Water II.

## Problem description

Given an `m × 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 pounds 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`

## Problem analysis

We can see that if there is any water trapped in the area, the water will be trapped in one or more regions. Each region's water height is constrained by the cell on the region's boundary with lowest height. Also, all cells that can trap water cannot be on the edges or corners.

For these reasons, we need to keep track of the boundaries as we iterate through all the cells to find out how much amount of water each cell will trap. We also notice the need to find the boundary cell with smallest height, since the lowest-height cell on the boundary will directly determine the water level of its neighboring region. So the suitable data structure for this is the min-heap.

We also need to keep track of whether a cell has been visited, in order to avoid double processing of the same cell.

So we can create a `Cell` class:

```
class Cell {
public:
 const int row, col, height;

 bool visited = false;

 // if waterHeight > height: height of water after trapping water
 // otherwise: cannot trap water / water amount is undecided
 int waterHeight = 0;

 Cell(int row, int col, int height): row(row), col(col), height(height) {}
};

```

* The fields `row`, `col`, `height` come directly from input data and are immutable.
* `waterHeight` means the level of water after raining.
	+ If `waterHeight > cell.height` then `waterHeight` represents the computed level of water after rain.
	+ Otherwise it means that the cell cannot trap water, or the amount of water it can trap is undecided.
* `visited` means whether the cell has been visited during the iterations of computing water amounts.

The entire procedure will be as follows:

1. Initialize `totalWater` to 0.
2. Create the m × n `cells` matrix, with each item being an instance of class `Cell`.
3. Create the min-heap `boundaries` to keep track of the boundary cells to work on.
4. At the beginning of the iterations, mark all corners and edges as `visited`, and add all edges to the `boundaries`.
5. While `boundaries` is not empty:
	1. Pop the top of the heap, i.e. the cell `curr` with minimum height.
	2. `curr.waterHeight` is either 0 or the previously computed water height. We can let `waterHeight = max(curr.waterHeight, curr.Height)`.
	3. Inspect each neighbor (up / down / left / right) of `curr`. Add each neighbor not yet visited to the min-heap, mark it as `visited`, and set its `waterHeight`. This is because we want to always ensure that we still have a valid boundary after shrinking the boundaries, and that each single cell is processed exactly once.
		* If `neighbor.height >= curr.height`, then `neighbor` cannot trap water.
		* Otherwise set `neighbor.height = waterHeight`. Then add `waterHeight - neighbor.height` to `totalWater`.
6. After processing all cells, return `totalWater` as the result.

### Complexity analysis

Time complexity
We have two major parts:
1. Creating the matrix of cells. Time complexity is O(*m* ⋅ *n*).
2. Each cell (excluding the corner cells) will be added once into the min-heap and popped once.
	* Insertion of single cell: O(log(*m* ⋅ *n*)).
	* Deletion of minimum height cell: O(1).
	* Generating neighbors: O(1).
	* So the total time complexity on heap operations is O(*m* ⋅ *n* ⋅ log(*m* ⋅ *n*)).

So the total time complexity is **O(*m* ⋅ *n* ⋅ log(*m* ⋅ *n*))**.
Space complexity

* The space complexity for cell matrix is O(*m* ⋅ *n*).
* The space complexity for the min heap is also O(*m* ⋅ *n*), since it can only contain at most as many items as in input.

So the total space complexity is O(*m* ⋅ *n*).
## Solution

### C++ solution

```
class Solution {
private:
 class Cell {
 public:
 const int row, col, height;

 bool visited = false;

 // if waterHeight > height: height of water after trapping water
 // otherwise: cannot trap water / water amount is undecided
 int waterHeight = 0;

 Cell(int row, int col, int height): row(row), col(col), height(height) {}
 };

 class CellComp {
 public:
 bool operator()(Cell\* c1, Cell\* c2) {
 return c1 && c2 && (c1->height > c2->height);
 }
 };

 typedef std::vector<std::vector<Cell\*>> Cells;
 typedef std::priority\_queue<Cell\*, std::vector<Cell\*>, CellComp> MinHeap;

 bool checkAddCell(Cells& cells, MinHeap& boundary, int row, int col) {
 // step 1: check range
 const int rows = cells.size(), cols = cells[0].size();
 return false;
 }
 // step 2: check visited
 Cell \*cell = cells[row][col];
 if (cell->visited) {
 return false;
 }
 // step 3: all checks passed, push to boundary (min heap)
 boundary.push(cell);
 cell->visited = true;
 return true;
 }

public:
 int trapRainWater(std::vector<std::vector<int>>& heightMap) {
 const int rows = heightMap.size();
 if (rows <= 2) {
 return 0;
 }
 const int cols = heightMap[0].size();
 if (cols <= 2) {
 return 0;
 }

 // create a matrix to store all cells
 // use a min heap to keep track of boundary cells
 Cells cells(rows);
 MinHeap boundary;
 // put all cells into the matrix
 for (int i = 0; i < rows; ++i) {
 for (int j = 0; j < cols; ++j) {
 Cell \*cell = new Cell(i, j, heightMap[i][j]);
 cells[i].emplace\_back(cell);

 // put boundary cells into the min heap
 // case 1: first and last rows
 if ((i == 0) || (i == rows - 1)) {
 cell->visited = true;
 if ((j > 0) && (j < cols - 1)) {
 boundary.push(cell);
 }
 }
 // case 2: middle rows
 else if ((j == 0) || (j == cols - 1)) {
 cell->visited = true;
 boundary.push(cell);
 }
 }
 }

 int totalWater = 0;

 // find water amount by pushing the boundaries
 // for each iteration, start from the cell on boundary with minimum height
 while (!boundary.empty()) {
 Cell \*curr = boundary.top();
 boundary.pop();

 int waterHeight = std::max(curr->waterHeight, curr->height);

 // check if water will be trapped into neighbor cells
 // i.e. up / down / left / right
 // the constraint height is waterHeight
 const int row = curr->row, col = curr->col;
 for (const auto& i: {-1, 0, 1}) {
 for (const auto& j: {-1, 0, 1}) {
 if (!((i != 0) ^ (j != 0))) {
 continue;
 }
 // check if each neighbor will be added as new boundary
 if (checkAddCell(cells, boundary, row + i, col + j)) {
 Cell \*neighbor = cells[row+i][col+j];
 // compute water height if the neighbor will actually trap water
 if (neighbor->height < waterHeight) {
 neighbor->waterHeight = waterHeight;
 totalWater += waterHeight - neighbor->height;
 }
 }
 }
 }
 }

 // clean up
 for (auto& row: cells) {
 for (auto& cell: row) {
 delete cell;
 }
 }

 return totalWater;
 }
};

```

### Java solution

```
class Solution {
 private static class Cell {
 final int row, col, height;

 boolean visited = false;

 // if waterHeight > height: height of water after trapping water
 // otherwise: cannot trap water / water amount is undecided
 int waterHeight = 0;

 Cell(int row, int col, int height) {
 this.row = row;
 this.col = col;
 this.height = height;
 }
 }

 private static class CellComp implements Comparator<Cell> {
 public int compare(Cell c1, Cell c2) {
 if ((c1 != null) && (c2 != null)) {
 return Integer.compare(c1.height, c2.height);
 }
 return 0;
 }
 }

 private boolean checkAddCell(Cell[][] cells, PriorityQueue<Cell> boundary,
 int row, int col) {
 // step 1: check range
 final int rows = cells.length, cols = cells[0].length;
 return false;
 }
 // step 2: check visited
 Cell cell = cells[row][col];
 if (cell.visited) {
 return false;
 }
 // step 3: all checks passed, push to boundary (min heap)
 boundary.offer(cell);
 cell.visited = true;
 return true;
 }

 public int trapRainWater(int[][] heightMap) {
 final int rows = heightMap.length;
 if (rows <= 2) {
 return 0;
 }
 final int cols = heightMap[0].length;
 if (cols <= 2) {
 return 0;
 }

 // create a matrix to store all cells
 // use a min heap to keep track of boundary cells
 Cell[][] cells = new Cell[rows][cols];
 PriorityQueue<Cell> boundary = new PriorityQueue<>(new CellComp());
 // put all cells into the matrix
 for (int i = 0; i < rows; ++i) {
 for (int j = 0; j < cols; ++j) {
 Cell cell = new Cell(i, j, heightMap[i][j]);
 cells[i][j] = cell;

 // put boundary cells into the min heap
 // case 1: first and last rows
 if ((i == 0) || (i == rows - 1)) {
 cell.visited = true;
 if ((j > 0) && (j < cols - 1)) {
 boundary.offer(cell);
 }
 }
 // case 2: middle rows
 else if ((j == 0) || (j == cols - 1)) {
 cell.visited = true;
 boundary.offer(cell);
 }
 }
 }

 int totalWater = 0;

 // find water amount by pushing the boundaries
 // for each iteration, start from the cell on boundary with minimum height
 while (!boundary.isEmpty()) {
 Cell curr = boundary.poll();

 int waterHeight = Integer.max(curr.waterHeight, curr.height);

 // check if water will be trapped into neighbor cells
 // i.e. up / down / left / right
 // the constraint height is waterHeight
 final int row = curr.row, col = curr.col;
 for (int i: new int[]{-1, 0, 1}) {
 for (int j: new int[]{-1, 0, 1}) {
 if (!((i != 0) ^ (j != 0))) {
 continue;
 }
 // check if each neighbor will be added as new boundary
 if (checkAddCell(cells, boundary, row + i, col + j)) {
 Cell neighbor = cells[row+i][col+j];
 // compute water height if the neighbor will actually trap water
 if (neighbor.height < waterHeight) {
 neighbor.waterHeight = waterHeight;
 totalWater += waterHeight - neighbor.height;
 }
 }
 }
 }
 }

 return totalWater;
 }
}

```

## Notes

* For priority queues (heaps), both Java and C++ allow us to use custom comparators to compare items in the heap. But the main differences are:
	+ C++ uses `bool` as the return type of comparator, whereas Java uses `int`.
	+ In C++, if we compare items `a1` and `a2` using the custom comparator and the result is `true`, then item `a1` has lower priority than `a2`, i.e. `a2` will go before `a1` when being popped.
	+ In Java, if we compare `a1` and `a2`, then
		- `result > 0`: `a1` has lower priority than `a2` (being popped later than `a2`).
		- `result == 0`: `a1` has the same priority as `a2`.
		- `result < 0`: `a1` has higher priority than `a2` (being popped earlier than `a2`).
	+ C++'s comparator class is bundled as one of the type parameters of `std::priority_queue`. It also must overload `operator()` to be callable as a function, which returns a `bool` value. (See https://en.cppreference.com/w/cpp/container/priority\_queue for more details.)
	+ Java's comparator class implements the interface `Comparator`. It must implement the `compare` method, which has the return type `int`. Unlike C++, Java's `PriorityQueue` class does not contain the comparator class as part of the typing information.