3d接雨水问题

678 阅读4分钟

3D 接雨水问题是接雨水问题的一个扩展,其中我们不仅考虑一维情况(即柱子),而是考虑一个三维的网格或立体结构。在这种情况下,我们的目标是计算一个由不同高度的单元格构成的区域能够接住多少水。


问题描述

给定一个 m x n 的二维网格 height[][],其中 height[i][j] 表示坐标 (i, j) 处的柱子的高度。假设水可以从这些高度的周围区域流动并充满空隙,计算最终能够被这些柱子围住的水量。


解题思路

这个问题的难点在于我们不仅要考虑每个位置左、右的最大高度,还要考虑到四个方向(上、下、左、右)的影响。为了处理这种情况,我们使用一个 优先队列(最小堆)BFS(广度优先搜索) 来逐步模拟水的流动。

步骤

  1. 模拟水的流动:我们可以从边界开始模拟水流入内部的过程。水流是从低处向高处流动的,因此我们需要优先处理边界的低高度,逐步将水流入内部区域。
  2. 优先队列:使用一个最小堆(优先队列)来始终处理当前边界中最小的高度,这样可以确保水总是从最低的位置开始流入。
  3. 四个方向的流动:每次从当前的最小高度出发,检查它的四个邻接单元格(上下左右)。如果邻接单元格的高度比当前高度低,则能积水,积水的量为当前高度和邻接单元格高度的差。
  4. 更新边界:每次处理完一个单元格后,将它的邻接单元格加入队列,以便下一轮处理。

算法步骤

  1. 将所有边界单元格加入最小堆。
  2. 从堆中弹出当前最小的单元格,检查它的四个邻接单元格。
  3. 如果邻接单元格的高度小于当前单元格,则可以积水,并更新该邻接单元格的高度为当前单元格的高度(即水流将其“填充”)。
  4. 继续重复这个过程,直到所有单元格都被处理完。

时间复杂度

  • 假设网格大小为 m×nm。
  • 每个单元格最多被处理一次,插入堆的操作是 O(log⁡(m×n)),因此总时间复杂度是 O(m×nlog⁡(m×n))。

代码实现

#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>

using namespace std;

struct Cell {
    int x, y, height;
    Cell(int x, int y, int height) : x(x), y(y), height(height) {}
    
    // 用于最小堆比较
    bool operator<(const Cell& other) const {
        return height > other.height;  // 小的高度优先
    }
};

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

    int m = heightMap.size(), n = heightMap[0].size();
    priority_queue<Cell> minHeap;

    // 记录哪些单元格已被访问过
    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 || i == m - 1 || j == 0 || j == n - 1) {
                minHeap.push(Cell(i, j, heightMap[i][j]));
                visited[i][j] = true;
            }
        }
    }

    int waterTrapped = 0;
    // 四个方向:上、下、左、右
    vector<int> directions = {-1, 0, 1, 0, -1};

    while (!minHeap.empty()) {
        Cell curr = minHeap.top();
        minHeap.pop();

        // 检查四个方向的邻接单元格
        for (int i = 0; i < 4; ++i) {
            int nx = curr.x + directions[i];
            int ny = curr.y + directions[i + 1];

            // 判断是否越界
            if (nx < 0 || nx >= m || ny < 0 || ny >= n || visited[nx][ny]) continue;

            // 如果当前位置的高度小于当前单元格的高度,则积水
            if (heightMap[nx][ny] < curr.height) {
                waterTrapped += curr.height - heightMap[nx][ny];
            }

            // 更新邻接单元格的高度(模拟水填充)
            minHeap.push(Cell(nx, ny, max(heightMap[nx][ny], curr.height)));
            visited[nx][ny] = true;
        }
    }

    return waterTrapped;
}

int main() {
    vector<vector<int>> heightMap = {
        {1,4,3,1,3,2},
        {3,2,1,3,2,4},
        {2,3,3,2,3,1}
    };
    
    cout << "Total rainwater trapped: " << trapRainWater(heightMap) << endl;
    return 0;
}

复杂度分析

  1. 时间复杂度

    • 我们需要对每个单元格进行一次操作,并将每个单元格加入最小堆中,堆的大小最多为 m×nm ,所以时间复杂度为 O(m×nlog⁡(m×n))。
  2. 空间复杂度

    • 使用了一个最小堆来存储单元格,大小为 O(m×n),此外还需要一个访问标记数组,空间复杂度为 O(m×n)。

总结

  • 3D 接雨水问题的关键在于从边界开始模拟水的流动,并使用最小堆来确保每次处理最低的边界单元格。
  • 通过逐步填充内部分低洼区域,能够计算出最终的接水量。