在计算机科学里,有些问题乍一看似乎只是“编程练习”,但深入研究之后,却能触及到核心的算法思想、空间时间优化策略,乃至程序设计中最本质的“建模方法”。“接雨水问题”(Trapping Rain Water)就是这样一个看似简单,实则深刻的问题。

想象一下,一排高低错落的柱子,雨后水会积在哪些地方?这个现实生活中的问题,被抽象成了一个算法挑战。它不仅考察你的代码能力,还要求你理解数据结构、算法效率、甚至空间思维。更有趣的是,这个问题虽然可以暴力解决,但也可以用优雅的算法进行优化,每一种解法都体现了计算思维的不同维度。

接雨水问题是什么?怎么解决? 接雨水 | 算法_Python

1. 问题描述

在现代城市中,我们经常看到下雨之后街道两旁的积水。积水的多少不仅取决于地面是否平坦,更取决于地势高低。高楼之间的低洼区域,就像自然形成的水池一样,能够“收集”雨水。

这个现象被极其形象地抽象为一个算法问题:给你一个数组 height,每个元素代表一根柱子的高度,柱子宽度为 1。下雨后,水将积存在柱子之间的低洼区域。你的任务是计算总共能接多少单位的雨水。

例如:

输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
  • 1.
  • 2.

看起来像是一个“图形问题”,但算法的本质不只是图形,而是如何通过结构化的数据建模来实现空间判断与水量计算。

2. 问题建模:从地形到数学模型

我们可以把每个 height[i] 理解为一个柱子的高度,那么水的积累发生在什么地方?某个位置 i 处能积水的前提是:

  • 它左边有比它高的柱子;
  • 它右边也有比它高的柱子;
  • 此位置不是最高点。

公式化描述:

water[i] = min(max_left[i], max_right[i]) - height[i]
  • 1.

其中:

  • max_left[i] 是位置 i 左边最高柱子的高度;
  • max_right[i] 是位置 i 右边最高柱子的高度;
  • 如果 min(max_left[i], max_right[i]) > height[i],则可以积水。

这就是一个典型的“区间极值分析”问题。接下来我们从不同角度出发,逐一实现各类解法。

3. 解法一:暴力解法(Brute Force)

这种方法直接按照公式定义来实现,对每个柱子左右遍历找最大高度。

def trap(height):
    n = len(height)
    res = 0
    for i in range(1, n - 1):
        max_left = max(height[:i])
        max_right = max(height[i+1:])
        water = min(max_left, max_right) - height[i]
        if water > 0:
            res += water
    return res
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

分析:

  • 时间复杂度:O(n²)
  • 空间复杂度:O(1)

虽然思路朴素,但在大规模数据下效率低下,容易超时。

4. 解法二:动态规划优化(Dynamic Programming)

为了避免重复计算左右最大值,我们可以提前预处理两个数组:

def trap(height):
    n = len(height)
    if n == 0:
        return0

    left_max = [0]*n
    right_max = [0]*n
    left_max[0] = height[0]
    for i in range(1, n):
        left_max[i] = max(left_max[i-1], height[i])

    right_max[n-1] = height[n-1]
    for i in range(n-2, -1, -1):
        right_max[i] = max(right_max[i+1], height[i])

    res = 0
    for i in range(1, n-1):
        res += min(left_max[i], right_max[i]) - height[i]
    return res
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

分析:

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

通过预处理极大减少了不必要的计算,适合中等数据规模。

5. 解法三:双指针(Two Pointers)

极致优化在于空间。我们能不能不使用额外空间?答案是肯定的,核心思想是:从两边向中间移动指针,动态维护左右最大值。

def trap(height):
    ifnot height:
        return0

    left, right = 0, len(height) - 1
    left_max, right_max = height[left], height[right]
    res = 0

    while left < right:
        if height[left] < height[right]:
            left += 1
            left_max = max(left_max, height[left])
            res += max(0, left_max - height[left])
        else:
            right -= 1
            right_max = max(right_max, height[right])
            res += max(0, right_max - height[right])
    return res
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

分析:

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

这是一种空间效率极高的方法,尤其适用于面试场景与实际部署。

6. 解法四:单调栈(Monotonic Stack)

单调栈是一种极具启发性的解法,尤其适用于“找到左边第一个比当前元素大”的场景。

def trap(height):
    stack = []
    res = 0
    current = 0
    while current < len(height):
        while stack and height[current] > height[stack[-1]]:
            top = stack.pop()
            ifnot stack:
                break
            distance = current - stack[-1] - 1
            bounded_height = min(height[current], height[stack[-1]]) - height[top]
            res += distance * bounded_height
        stack.append(current)
        current += 1
    return res
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

分析:

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

这种方法的本质是模拟雨水“落入”低谷的过程,是许多高级算法题的基础。

7. 算法对比与选择策略

方法

时间复杂度

空间复杂度

优势

劣势

暴力法

O(n²)

O(1)

简单易懂

效率低,适用范围小

动态规划

O(n)

O(n)

高效,适用广泛

占用额外空间

双指针

O(n)

O(1)

空间最优

思维要求更高

单调栈

O(n)

O(n)

结构清晰,适合扩展

思维复杂,调试较难

8. 更进一步:问题的扩展与变种

这个问题可以拓展到二维:

  • 二维接雨水问题(Trapping Rain Water II):地图被抽象成一个二维矩阵,利用优先队列与 BFS 进行模拟。
  • 带障碍的接雨水问题:柱子之间有障碍物阻挡水流。
  • 不规则宽度的柱状图:柱子宽度不为 1。

这些变种不仅挑战更大,也更贴近真实世界中的物理建模。

9. 工程实践中的思维延展

在实际的工程场景中,类似的问题并不少见:

  • 城市雨水排放系统模拟;
  • 游戏地图中地形水流模拟;
  • 3D 建模与物理引擎中的流体计算;
  • 金融时间序列中“低点-高点”区间利润最大化等。

接雨水问题不仅是基础算法问题,更是工程建模的缩影。

10. 总结

通过分析“接雨水问题”,我们不仅学习了多种算法技巧,更重要的是理解了:

  • 如何用结构化思维建模物理问题;
  • 如何平衡时间与空间的权衡;
  • 如何从问题中抽象出核心逻辑。

这正是算法的魅力所在——在逻辑与现实之间架起桥梁。