力扣 42. 接雨水 - 高效双指针解法(Rust实现)详细题解

rust 复制代码
impl Solution {
    pub fn trap(height: Vec<i32>) -> i32 {
        if height.len()<3{
           return 0;
      }
    let mut left = 1;
    let mut right = height.len()-2;

    let mut leftmax = height[0];
    let mut rightmax = height[right+1];

    let mut res = 0;

    while left<=right{
        leftmax = leftmax.max(height[left]);
        rightmax = rightmax.max(height[right]);

        if leftmax<rightmax {
            res+=leftmax-height[left];
            left+=1;
        }else{
            res+=rightmax - height[right];
            right-=1;
        }
    }
    res
 }
}

💡 核心思想:木桶效应

要计算整个地形能接多少雨水,我们可以按列 来计算。对于任意一列 i,它能接住的雨水量取决于:
它左边最高的柱子(leftmax) 和 它右边最高的柱子(rightmax) 中较矮的那一个。

这就是经典的"木桶效应":
第 i 列的雨水量 = min(leftmax, rightmax) - height[i]
(注意:如果计算结果小于0,说明这列本身就是最高的,接不到水,记为0)。

如果采用暴力或动态规划,我们需要 O(N)O(N) 的额外空间来存储每个位置的左右最大值。而双指针法通过在两端向中间逼近,巧妙地将空间复杂度降到了 O(1)O(1)。


🚀 代码逐段原理解析

1. 边界防御,防止越界

复制代码
if height.len() < 3 {
    return 0;
}

接雨水至少需要 3 根柱子(两边高,中间低)才能形成凹槽。这个前置判断不仅是逻辑优化,更重要的是保护了后续代码 。因为下面用到了 height.len() - 2,如果不拦截长度小于 3 的情况,在 Rust 中会因为 usize 溢出而引发 Panic。

2. 指针初始化

复制代码
let mut left = 1;
let mut right = height.len() - 2;

let mut leftmax = height[0];
let mut rightmax = height[right + 1]; // 即 height[height.len() - 1]

这是一个非常棒的细节优化!

最左侧 height[0] 和最右侧 height[len-1] 作为边界,它们上面是绝对不可能积水的。

所以你直接将它们作为初始的 leftmax 和 rightmax,并将 left 和 right 指针向内收缩一位(从 1 和 len-2 开始遍历)。这省去了对无效边界的计算。

3. 双指针的核心

复制代码
while left <= right {
    // 1. 同步更新当前左右两侧的最大高度
    leftmax = leftmax.max(height[left]);
    rightmax = rightmax.max(height[right]);

    // 2. 比较 leftmax 和 rightmax,决定处理哪一边
    if leftmax < rightmax {
        res += leftmax - height[left];
        left += 1;
    } else {
        res += rightmax - height[right];
        right -= 1;
    }
}

这是整个算法的灵魂所在,为什么可以这样写?

  • 为什么可以只看一边的最大值?
    假设当前 leftmax < rightmax,对于 left 指针所在的位置来说,它的左侧最高确定是 leftmax。虽然它的右侧可能还有比 rightmax 更高的柱子,但这已经不重要了 ,因为"木桶"的短板已经确定是 leftmax。因此,我们可以放心地计算 left 处的积水量,并将 left 指针向右移。反之亦然。
  • 如何避免负数积水?
    注意这行代码:leftmax = leftmax.max(height[left]);。
    因为你在计算积水 leftmax - height[left] 之前 ,先更新了 leftmax。这就保证了 leftmax 永远大于等于当前的 height[left]。如果当前柱子是迄今为止最高的,leftmax 就会等于 height[left],相减得 0,完美符合物理现实(高山顶上不积水),无需写繁琐的 if 去防止负数。

📊 复杂度分析

  • 时间复杂度:O(N)O(N)
    其中 NN 是数组 height 的长度。left 和 right 指针相向而行,每个元素最多被访问一次,整个过程只有一次遍历。
  • 空间复杂度:O(1)O(1)
    只使用了 left、right、leftmax、rightmax、res 等几个标量类型的变量,没有开辟额外的数组空间。空间复杂度达到了理论最优。
相关推荐
longlongzihan9 小时前
LeetCode 24.两两交换链表中的节点:虚拟头结点与指针
算法·leetcode·链表
residual_fan9 小时前
基于控制感知域对抗网络的变工况故障诊断:从控制系统与运行状态协同的角度建立模型
人工智能·算法·数据挖掘·数据分析
Xin7709 小时前
LeetCode 105. 从前序与中序遍历序列构造二叉树(递归分治法)
leetcode
翻山越岭的少年9 小时前
用控制信号理解状态变化:基于控制感知域对抗网络的变工况故障诊断
算法
longlongzihan9 小时前
从LeetCode 19. 删除链表的倒数第 N 个结点深入理解“哨兵节点”
c++·算法·leetcode·链表
DeroZan9 小时前
# jieba 分词 & 文本语料分析 & Python 内置函数笔记
算法
Gigavision9 小时前
CAS(ME)²数据集 感知图卷积网络算法 CAS(ME)²数据集下载
网络·算法
杜 硕10 小时前
快速排序02
算法·排序算法
careathers10 小时前
【数据结构】二叉树拓展
数据结构·算法
java_nnnn10 小时前
LeetCode刷题滑动窗口(长度最小的子数组)
算法·leetcode·职场和发展