【LeetCode】42. 接雨水
解题思路
接雨水问题的核心在于计算每个位置能承载的雨水量,这取决于该位置左侧最高柱子和右侧最高柱子中较矮的那个(木桶效应)。以下是三种主流解法及双指针法的优化实现:
- 暴力解法:每个位置遍历左右两侧找最高柱子,时间复杂度 O(n²),空间复杂度 O(1)。
 - 动态规划:预处理左右最大值数组,时间复杂度 O(n),空间复杂度 O(n)。
 - 双指针法:动态维护左右指针和极值,时间复杂度 O(n),空间复杂度 O(1)。
 
双指针法是最优解,通过左右指针向中间移动,每次处理较矮的一侧,实时更新极值并计算水量。
关键步骤
- 
初始化变量:
left和right指针分别从数组两端开始。leftMax和rightMax记录左右遍历过程中的最大高度。water累加总雨水量。
 - 
移动策略:
- 比较 
height[left]和height[right],选择较矮的一侧处理。 - 若当前高度小于对应侧的极值,计算雨水量;否则更新极值。
 
 - 比较 
 - 
终止条件:当左右指针相遇时结束循环。
 
Golang代码实现
package main import "fmt" func trap(height []int) int { n := len(height) if n <= 2 { return 0 // 边界处理:至少需要3根柱子才能形成凹槽 } left, right := 0, n-1 // 双指针初始化 leftMax, rightMax := 0, 0 water := 0 for left < right { // 选择较矮的一侧处理(木桶效应) if height[left] < height[right] { if height[left] > leftMax { leftMax = height[left] // 更新左侧最高挡板 } else { water += leftMax - height[left] // 当前列积水量 } left++ } else { if height[right] > rightMax { rightMax = height[right] // 更新右侧最高挡板 } else { water += rightMax - height[right] // 当前列积水量 } right-- } } return water }
代码解析
- 双指针策略:左右指针向中间移动,每次处理较矮的一侧,确保水量计算基于可靠的最小极值。
 - 极值更新:
leftMax和rightMax动态更新,分别记录遍历过程中左右侧的最大高度。 - 水量计算:若当前高度低于对应侧极值,累加 
极值 - 当前高度作为雨水量。 
复杂度分析
| 指标 | 值 | 说明 | 
|---|---|---|
| 时间复杂度 | O(n) | 单次遍历数组 | 
| 空间复杂度 | O(1) | 仅需常数级变量 | 
运行示例
func main() { // 示例1验证 height1 := []int{0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1} fmt.Println("示例1输出:", trap(height1)) // 输出: 6 // 示例2验证 height2 := []int{4, 2, 0, 3, 2, 5} fmt.Println("示例2输出:", trap(height2)) // 输出: 9 }
核心逻辑验证
以示例1 [0,1,0,2,1,0,1,3,2,1,2,1] 为例:
- 初始状态:
left=0,right=11,leftMax=0,rightMax=1。 - 左指针移动:
left=1(高度1),更新leftMax=1,无积水。 - 右指针移动:处理高度1的右端,计算 
rightMax并累加水量。 - 最终累计:遍历至中间位置时,左右极值动态更新,计算所有凹槽的水量总和为6。
 
总结
双指针法通过动态维护极值和单次遍历,实现了时间和空间的最优解。其核心在于:
- 木桶效应:水量由较矮一侧的极值决定。
 - 边界处理:无需预处理数组,直接通过指针移动计算。
 
                    
                
                
            
        
浙公网安备 33010602011771号