☰
LeetCode接雨水问题:动态规划与双指针解法详解
2026/10/28 17:22:09 网站建设 项目流程

1. 问题背景与核心挑战

"接雨水"是LeetCode Hot100系列中的经典题目(编号42),也是面试中高频出现的算法考题。题目描述如下:给定n个非负整数表示每个宽度为1的柱子的高度图,计算按此排列的柱子下雨之后能接多少雨水。

这个问题的实际应用场景非常广泛,比如:

  • 城市规划中的排水系统设计
  • 农业灌溉中的蓄水计算
  • 建筑设计中屋顶雨水收集方案

核心难点在于如何高效计算每个"凹槽"能存储的雨水量。新手常见的误区是只关注局部凹陷,而忽略了整体地形对蓄水量的影响。我在第一次遇到这个问题时,就曾陷入暴力遍历的陷阱,导致时间复杂度高达O(n²)。

2. 解法思路分析与对比

2.1 暴力解法(不推荐)

最直观的思路是对每个柱子,找到其左右两侧的最高柱子,取较小值减去当前柱子高度。这种方法虽然容易理解,但时间复杂度为O(n²),在LeetCode上会超时。

def trap(height): res = 0 for i in range(1, len(height)-1): left_max = max(height[:i]) right_max = max(height[i+1:]) res += max(0, min(left_max, right_max) - height[i]) return res

注意:实际面试中如果只给出这种解法,通常会被要求优化。建议至少掌握下面两种高效解法。

2.2 动态规划解法(推荐)

通过预处理存储每个位置的左右最大值,可以将时间复杂度优化到O(n),空间复杂度O(n)。

def trap(height): if not height: return 0 n = len(height) 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[-1] = height[-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(n): res += min(left_max[i], right_max[i]) - height[i] return res

这个解法的核心在于:

  1. 从左向右扫描,记录每个位置左侧的最大高度
  2. 从右向左扫描,记录每个位置右侧的最大高度
  3. 最后遍历每个位置,计算积水高度

2.3 双指针解法(最优)

进一步优化空间复杂度到O(1)的双指针解法:

def trap(height): if not height: return 0 left, right = 0, len(height)-1 left_max = right_max = 0 res = 0 while left < right: if height[left] < height[right]: if height[left] >= left_max: left_max = height[left] else: res += left_max - height[left] left += 1 else: if height[right] >= right_max: right_max = height[right] else: res += right_max - height[right] right -= 1 return res

这个解法的精妙之处在于:

  • 左右指针向中间移动
  • 每次移动较矮一侧的指针
  • 实时更新左右最大值
  • 只在当前高度小于对应侧最大值时才计算积水

3. 关键细节与边界处理

3.1 边界条件处理

实际编码时需要特别注意以下边界情况:

  1. 空数组输入:直接返回0
  2. 单调递增/递减序列:无法积水,应返回0
  3. 所有柱子等高:无法积水,应返回0

3.2 计算逻辑验证

可以通过这个简单例子验证算法正确性: 输入:[0,1,0,2,1,0,1,3,2,1,2,1] 可视化:

■ ■ ■■ ■ ■ ■■ ■■■■■■ ------------ 012345678901

正确输出应为6。可以手动计算每个位置的积水量验证。

4. 复杂度分析与优化空间

4.1 时间复杂度

  • 暴力解法:O(n²)
  • 动态规划:O(n)
  • 双指针:O(n)

4.2 空间复杂度

  • 暴力解法:O(1)
  • 动态规划:O(n)
  • 双指针:O(1)

4.3 其他优化思路

对于特别大的输入数据,可以考虑:

  1. 并行计算左右最大值
  2. 使用位运算加速比较操作
  3. 提前终止条件:当左右指针相遇或交叉时

5. 常见错误与调试技巧

5.1 典型错误案例

  1. 忘记处理空输入
  2. 边界索引越界(如访问height[-1]或height[n])
  3. 错误更新最大值(应该在计算积水前更新)
  4. 整数溢出(虽然Python不用担心,但其他语言需要注意)

5.2 调试方法

建议使用这些测试用例调试:

  1. [] → 0
  2. [1] → 0
  3. [1,0,1] → 1
  4. [1,2,3,4] → 0
  5. [4,3,2,1] → 0
  6. [2,0,2] → 2

5.3 可视化调试技巧

可以打印中间变量:

print(f"i={i}, left_max={left_max}, right_max={right_max}, res={res}")

或者绘制ASCII图辅助理解:

def print_heights(height): max_h = max(height) for h in range(max_h, 0, -1): print(''.join('■' if x >= h else ' ' for x in height)) print('-' * len(height))

6. 实际工程应用扩展

虽然这是算法题,但其思想可以应用于:

  1. 实时雨水收集系统设计
  2. 地形分析中的洼地检测
  3. 图像处理中的区域填充算法
  4. 金融分析中的支撑/阻力位识别

例如在智慧农业中,我们可以用类似算法计算农田的蓄水能力,结合土壤湿度数据优化灌溉方案。

7. 刷题进阶建议

掌握这道题后,可以尝试这些相关题目:

  • 84.柱状图中最大的矩形
  • 407.接雨水II(3D版本)
  • 11.盛最多水的容器
  • 755.倒水问题

我个人的刷题经验是:同类题目集中练习,对比不同解法的优劣,并记录每种解法的最佳适用场景。这道"接雨水"就属于"数组+双指针/动态规划"的经典题型,掌握后对理解其他类似题目大有裨益。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询