接雨水问题 | 豆包MarsCode AI刷题

130 阅读3分钟

1D 接雨水问题(也叫 Trapping Rain Water)是一个经典的动态规划和栈的应用题。其目标是计算给定一系列柱子的高度值,能够接多少雨水。


问题描述

给定一个整数数组 height[],表示柱子的高度,数组的每个元素 height[i] 表示第 iii 个柱子的高度。雨水会在两柱子之间的空隙中积水,且积水的量由两柱子的最小高度和它们之间的宽度决定。计算并返回该数组所能接的雨水总量。

思路

1D 接雨水问题的关键点是确定每个位置能接的雨水量。具体来说,某个位置能够存储的水量,取决于该位置左边和右边的最大高度。

核心思想

  • 当前位置接水的高度:对于任意位置 iii,它接的水量取决于当前位置两边的最高柱子。设:

    • left_max[i]:从左边到位置 iii 的最大高度。
    • right_max[i]:从右边到位置 iii 的最大高度。

    那么,在位置 iii 能接的水量是:

    water_trapped[i]=max⁡(0,min⁡(left_max[i],right_max[i])−height[i])

    这个式子的含义是:当前位置 iii 能接的水量是左边和右边的最大高度的较小值减去当前位置的高度。

  • 步骤

    • 第一步:构建 left_max 数组,记录每个位置左侧最高的柱子。
    • 第二步:构建 right_max 数组,记录每个位置右侧最高的柱子。
    • 第三步:遍历每个位置,计算当前位置的接水量。

时间复杂度

  • 构建 left_max 数组:O(n)O(n)O(n)
  • 构建 right_max 数组:O(n)O(n)O(n)
  • 计算接水量:O(n)O(n)O(n)

总体时间复杂度是 O(n)O(n)O(n),其中 nnn 是柱子的数量。

代码实现

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

using namespace std;

int trap(vector<int>& height) {
    int n = height.size();
    if (n == 0) return 0;

    // 创建 left_max 和 right_max 数组
    vector<int> left_max(n), right_max(n);
    
    // 填充 left_max 数组
    left_max[0] = height[0];
    for (int i = 1; i < n; ++i) {
        left_max[i] = max(left_max[i - 1], height[i]);
    }

    // 填充 right_max 数组
    right_max[n - 1] = height[n - 1];
    for (int i = n - 2; i >= 0; --i) {
        right_max[i] = max(right_max[i + 1], height[i]);
    }

    // 计算接水量
    int water = 0;
    for (int i = 0; i < n; ++i) {
        water += min(left_max[i], right_max[i]) - height[i];
    }

    return water;
}

int main() {
    vector<int> height = {0,1,0,2,1,0,1,3,2,1,2,1};
    cout << "Total water trapped: " << trap(height) << endl;
    return 0;
}

优化:空间复杂度优化

上面的解法需要额外的两个数组 left_maxright_max,空间复杂度为 O(n)O(n)O(n)。我们可以通过双指针的方式来优化空间复杂度,将其优化到 O(1)O(1)O(1)。

空间优化思路

  1. 使用两个指针 leftright 分别指向数组的两端,计算左侧最大值和右侧最大值。
  2. 使用变量 left_maxright_max 来存储当前的最大高度。
  3. 根据当前的高度和最大值计算每个位置的接水量。

优化后代码

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

using namespace std;

int trap(vector<int>& height) {
    int n = height.size();
    if (n == 0) return 0;

    int left = 0, right = n - 1;
    int left_max = 0, right_max = 0;
    int water = 0;

    while (left <= right) {
        if (height[left] <= height[right]) {
            if (height[left] >= left_max) {
                left_max = height[left];
            } else {
                water += left_max - height[left];
            }
            left++;
        } else {
            if (height[right] >= right_max) {
                right_max = height[right];
            } else {
                water += right_max - height[right];
            }
            right--;
        }
    }

    return water;
}

int main() {
    vector<int> height = {0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1};
    cout << "Total water trapped: " << trap(height) << endl;
    return 0;
}

总结

  1. 1D 接雨水问题的核心是计算每个位置能接的水量。
  2. 动态规划通过预计算每个位置的左侧最大值和右侧最大值,能够高效求解。
  3. 优化空间复杂度:使用双指针可以将空间复杂度优化为 O(1)O(1)O(1),并且时间复杂度保持为 O(n)O(n)O(n)。

通过以上方法,可以非常高效地解决接雨水问题,适用于各种输入规模。