最近逛脉脉,刷到了一个热题,3D 接雨水,正好咱们之前看过 2D 的,这次就来看看这个 3D 的。
因为做过 2D 的,所以我顺着之前的思路有点想法:
- 想法一: 2D 时,可以聚焦于先算出当前这个柱子能装多少水,等于左边最高柱子和右边最高柱子中较低的那一个,那顺着这个思路,我想到了 3D 就是左边最高柱子和右边最高柱子、上边最高柱子和下边最高柱子这 4 个中较低的那一个。然而我写出思路提交时,连样例都没有通过,后面思考发现这种想法不对,因为 3D 时想要接到雨水需要周围的柱子都围住了才行,只是这四个是不够的。
- 想法二:放弃想法一之后,我又想到了 dfs 染色。对于 3D 的柱子,可以通过 Z 轴拆分维度,比如当 Z 等于 1 时,计算能接到的雨水,比如当 Z 等于 2 时,计算能接到的雨水,后面以此类推就能算出全部的雨水了,那 Z 等于 1 时怎么计算有多少雨水呢?可以用 dfs 染色,当有雨水时,这些雨水一定是不和外界联通的,即是被一圈墙围起来的,所以 dfs 跑一遍就能算出来了,但是这个方法超时了,因为时间复杂度是
2*10^4*200*200即8*10^8。
最终实在没想法了,就看了题解。思路是:把这个看成木桶接水,就像是短板理论:
最外层一圈一定接不到水,也就是可以看成木桶的边缘,先把木桶的边缘都放进优先队列中,这时取出最小的柱子,向这个柱子的 4 个方向扩散,扩散出的柱子有两种情况,如果比当前柱子大,那么不能接水,如果比当前柱子小,那么接到的水就是当前柱子高度减去扩散柱子的高度(为什么?因为当前柱子一定是边缘最小的了,所以它一定能接到水),后面在把扩散的柱子加入到优先队列中,已经比较完的柱子就移出队列,这样就又形成了新的桶的边缘,并且水桶面积也在不断缩小(当然要记录走过的柱子,防止重复走),最终就会计算完成。
说实话,这种想法很难想出来,所以这题就是多复习,多背诵吧 😂
最终代码如下:(我是用 JavaScript 写的)
function compare(a, b) {
return a[2] - b[2]
}
function trapRainWater(heightMap) {
let queue = new MyPriorityQueue(compare)
let m = heightMap.length
let n = heightMap[0].length
let vis = []
let res = 0
for (let i = 0; i < m; i++) {
vis[i] = []
for (let j = 0; j < n; j++) {
if (i === 0 || i === m - 1 || j === 0 || j === n - 1) {
queue.enqueue([i, j, heightMap[i][j]])
vis[i][j] = 1
}
}
}
while (!queue.isEmpty()) {
let d = [-1, 0, 1, 0, -1]
let ele = queue.dequeue()
for (let j = 0; j < 4; j++) {
let tx = ele[0] + d[j]
let ty = ele[1] + d[j + 1]
if (0 <= tx && tx < m && 0 <= ty && ty < n && vis[tx][ty] !== 1) {
if (heightMap[tx][ty] < ele[2]) {
res += ele[2] - heightMap[tx][ty]
}
vis[tx][ty] = 1
queue.enqueue([tx, ty, Math.max(heightMap[tx][ty], ele[2])])
}
}
}
return res
};
LeetCode 的 JS/TS 语言支持很拉胯,虽然说了能用三方库实现优先队列,但由于三方库没有更新导致很难用,会报错,所以我就自己去复制了三方库中最新的代码,用于实现优先队列,大家也直接复制我的工具代码即可:(通过这个工具代码发现了 datastructures-js 仓库,这里实现了 JavaScript 版的类似于 Java 的数据结构,刷题时很有用)
class Heap {
/**
* @param {function} compare
* @param {array} [_values]
* @param {number|string|object} [_leaf]
*/
constructor(compare, _values, _leaf) {
if (typeof compare !== 'function') {
throw new Error('Heap constructor expects a compare function');
}
this._compare = compare;
this._nodes = Array.isArray(_values) ? _values : [];
this._leaf = _leaf || null;
}
/**
* Converts the heap to a cloned array without sorting.
* @public
* @returns {Array}
*/
toArray() {
return Array.from(this._nodes);
}
/**
* Checks if a parent has a left child
* @private
*/
_hasLeftChild(parentIndex) {
const leftChildIndex = (parentIndex * 2) + 1;
return leftChildIndex < this.size();
}
/**
* Checks if a parent has a right child
* @private
*/
_hasRightChild(parentIndex) {
const rightChildIndex = (parentIndex * 2) + 2;
return rightChildIndex < this.size();
}
/**
* Compares two nodes
* @private
*/
_compareAt(i, j) {
return this._compare(this._nodes[i], this._nodes[j]);
}
/**
* Swaps two nodes in the heap
* @private
*/
_swap(i, j) {
const temp = this._nodes[i];
this._nodes[i] = this._nodes[j];
this._nodes[j] = temp;
}
/**
* Checks if parent and child should be swapped
* @private
*/
_shouldSwap(parentIndex, childIndex) {
if (parentIndex < 0 || parentIndex >= this.size()) {
return false;
}
if (childIndex < 0 || childIndex >= this.size()) {
return false;
}
return this._compareAt(parentIndex, childIndex) > 0;
}
/**
* Compares children of a parent
* @private
*/
_compareChildrenOf(parentIndex) {
if (!this._hasLeftChild(parentIndex) && !this._hasRightChild(parentIndex)) {
return -1;
}
const leftChildIndex = (parentIndex * 2) + 1;
const rightChildIndex = (parentIndex * 2) + 2;
if (!this._hasLeftChild(parentIndex)) {
return rightChildIndex;
}
if (!this._hasRightChild(parentIndex)) {
return leftChildIndex;
}
const compare = this._compareAt(leftChildIndex, rightChildIndex);
return compare > 0 ? rightChildIndex : leftChildIndex;
}
/**
* Compares two children before a position
* @private
*/
_compareChildrenBefore(index, leftChildIndex, rightChildIndex) {
const compare = this._compareAt(rightChildIndex, leftChildIndex);
if (compare <= 0 && rightChildIndex < index) {
return rightChildIndex;
}
return leftChildIndex;
}
/**
* Recursively bubbles up a node if it's in a wrong position
* @private
*/
_heapifyUp(startIndex) {
let childIndex = startIndex;
let parentIndex = Math.floor((childIndex - 1) / 2);
while (this._shouldSwap(parentIndex, childIndex)) {
this._swap(parentIndex, childIndex);
childIndex = parentIndex;
parentIndex = Math.floor((childIndex - 1) / 2);
}
}
/**
* Recursively bubbles down a node if it's in a wrong position
* @private
*/
_heapifyDown(startIndex) {
let parentIndex = startIndex;
let childIndex = this._compareChildrenOf(parentIndex);
while (this._shouldSwap(parentIndex, childIndex)) {
this._swap(parentIndex, childIndex);
parentIndex = childIndex;
childIndex = this._compareChildrenOf(parentIndex);
}
}
/**
* Recursively bubbles down a node before a given index
* @private
*/
_heapifyDownUntil(index) {
let parentIndex = 0;
let leftChildIndex = 1;
let rightChildIndex = 2;
let childIndex;
while (leftChildIndex < index) {
childIndex = this._compareChildrenBefore(
index,
leftChildIndex,
rightChildIndex
);
if (this._shouldSwap(parentIndex, childIndex)) {
this._swap(parentIndex, childIndex);
}
parentIndex = childIndex;
leftChildIndex = (parentIndex * 2) + 1;
rightChildIndex = (parentIndex * 2) + 2;
}
}
/**
* Inserts a new value into the heap
* @public
* @param {number|string|object} value
* @returns {Heap}
*/
insert(value) {
this._nodes.push(value);
this._heapifyUp(this.size() - 1);
if (this._leaf === null || this._compare(value, this._leaf) > 0) {
this._leaf = value;
}
return this;
}
/**
* Inserts a new value into the heap
* @public
* @param {number|string|object} value
* @returns {Heap}
*/
push(value) {
return this.insert(value);
}
/**
* Removes and returns the root node in the heap
* @public
* @returns {number|string|object}
*/
extractRoot() {
if (this.isEmpty()) {
return null;
}
const root = this.root();
this._nodes[0] = this._nodes[this.size() - 1];
this._nodes.pop();
this._heapifyDown(0);
if (root === this._leaf) {
this._leaf = this.root();
}
return root;
}
/**
* Removes and returns the root node in the heap
* @public
* @returns {number|string|object}
*/
pop() {
return this.extractRoot();
}
/**
* Applies heap sort and return the values sorted by priority
* @public
* @returns {array}
*/
sort() {
for (let i = this.size() - 1; i > 0; i -= 1) {
this._swap(0, i);
this._heapifyDownUntil(i);
}
return this._nodes;
}
/**
* Fixes node positions in the heap
* @public
* @returns {Heap}
*/
fix() {
// fix node positions
for (let i = Math.floor(this.size() / 2) - 1; i >= 0; i -= 1) {
this._heapifyDown(i);
}
// fix leaf value
for (let i = Math.floor(this.size() / 2); i < this.size(); i += 1) {
const value = this._nodes[i];
if (this._leaf === null || this._compare(value, this._leaf) > 0) {
this._leaf = value;
}
}
return this;
}
/**
* Verifies that all heap nodes are in the right position
* @public
* @returns {boolean}
*/
isValid() {
const isValidRecursive = (parentIndex) => {
let isValidLeft = true;
let isValidRight = true;
if (this._hasLeftChild(parentIndex)) {
const leftChildIndex = (parentIndex * 2) + 1;
if (this._compareAt(parentIndex, leftChildIndex) > 0) {
return false;
}
isValidLeft = isValidRecursive(leftChildIndex);
}
if (this._hasRightChild(parentIndex)) {
const rightChildIndex = (parentIndex * 2) + 2;
if (this._compareAt(parentIndex, rightChildIndex) > 0) {
return false;
}
isValidRight = isValidRecursive(rightChildIndex);
}
return isValidLeft && isValidRight;
};
return isValidRecursive(0);
}
/**
* Returns a shallow copy of the heap
* @public
* @returns {Heap}
*/
clone() {
return new Heap(this._compare, this._nodes.slice(), this._leaf);
}
/**
* Returns the root node in the heap
* @public
* @returns {number|string|object}
*/
root() {
if (this.isEmpty()) {
return null;
}
return this._nodes[0];
}
/**
* Returns the root node in the heap
* @public
* @returns {number|string|object}
*/
top() {
return this.root();
}
/**
* Returns a leaf node in the heap
* @public
* @returns {number|string|object}
*/
leaf() {
return this._leaf;
}
/**
* Returns the number of nodes in the heap
* @public
* @returns {number}
*/
size() {
return this._nodes.length;
}
/**
* Checks if the heap is empty
* @public
* @returns {boolean}
*/
isEmpty() {
return this.size() === 0;
}
/**
* Clears the heap
* @public
*/
clear() {
this._nodes = [];
this._leaf = null;
}
/**
* Implements an iterable on the heap
* @public
*/
[Symbol.iterator]( "Symbol.iterator") {
let size = this.size();
return {
next: () => {
size -= 1;
return {
value: this.pop(),
done: size === -1
};
}
};
}
/**
* Builds a heap from a array of values
* @public
* @static
* @param {array} values
* @param {function} compare
* @returns {Heap}
*/
static heapify(values, compare) {
if (!Array.isArray(values)) {
throw new Error('Heap.heapify expects an array of values');
}
if (typeof compare !== 'function') {
throw new Error('Heap.heapify expects a compare function');
}
return new Heap(compare, values).fix();
}
/**
* Checks if a list of values is a valid heap
* @public
* @static
* @param {array} values
* @param {function} compare
* @returns {boolean}
*/
static isHeapified(values, compare) {
return new Heap(compare, values).isValid();
}
}
class MyPriorityQueue {
/**
* Creates a priority queue
* @params {function} compare
*/
constructor(compare, _values) {
if (typeof compare !== 'function') {
throw new Error('MyPriorityQueue constructor expects a compare function');
}
this._heap = new Heap(compare, _values);
if (_values) {
this._heap.fix();
}
}
/**
* Returns an element with highest priority in the queue
* @public
* @returns {number|string|object}
*/
front() {
return this._heap.root();
}
/**
* Returns an element with lowest priority in the queue
* @public
* @returns {number|string|object}
*/
back() {
return this._heap.leaf();
}
/**
* Adds a value to the queue
* @public
* @param {number|string|object} value
* @returns {MyPriorityQueue}
*/
enqueue(value) {
return this._heap.insert(value);
}
/**
* Adds a value to the queue
* @public
* @param {number|string|object} value
* @returns {MyPriorityQueue}
*/
push(value) {
return this.enqueue(value);
}
/**
* Removes and returns an element with highest priority in the queue
* @public
* @returns {number|string|object}
*/
dequeue() {
return this._heap.extractRoot();
}
/**
* Removes and returns an element with highest priority in the queue
* @public
* @returns {number|string|object}
*/
pop() {
return this.dequeue();
}
/**
* Returns the number of elements in the queue
* @public
* @returns {number}
*/
size() {
return this._heap.size();
}
/**
* Checks if the queue is empty
* @public
* @returns {boolean}
*/
isEmpty() {
return this._heap.isEmpty();
}
/**
* Clears the queue
* @public
*/
clear() {
this._heap.clear();
}
/**
* Returns a sorted list of elements from highest to lowest priority
* @public
* @returns {array}
*/
toArray() {
return this._heap.clone().sort().reverse();
}
/**
* Implements an iterable on the priority queue
* @public
*/
[Symbol.iterator]( "Symbol.iterator") {
let size = this.size();
return {
next: () => {
size -= 1;
return {
value: this.pop(),
done: size === -1
};
}
};
}
/**
* Creates a priority queue from an existing array
* @public
* @static
* @returns {MyPriorityQueue}
*/
static fromArray(values, compare) {
return new MyPriorityQueue(compare, values);
}
}