两种思路的碰撞:从超时分层法到高效双指针的蜕变

一、问题回顾:接雨水问题的两种解法对比

题目要求:给定一个非负整数数组表示柱子的高度,计算这些柱子排列后能接多少雨水。

示例分析

以输入 [0,1,0,2,1,0,1,3,2,1,2,1] 为例,其结构如下:

可存储 6个单位的雨水。


二、分层法的局限:为何超时?

这里一开始我是想要算出每一层的面积,加起来,然后减去height的总和,剩下的就是水量了。

原思路代码
javascript 复制代码
function trap(height) {
    const n = height.length;
    if (n === 0) return 0;

    let totalWater = 0;
    let h = 1;
    let left = 0;
    let right = n - 1;

    while (true) {
        while (left < n && height[left] < h) {
            left++;
        }
        while (right >= 0 && height[right] < h) {
            right--;
        }
        // 检查
        if (left > right) break;
        // 计算当前层的有效宽度和统计符合条件的柱子数
        const currentWidth = right - left - 1;
        if (currentWidth <= 0) {
            h++;
            continue;
        }
        let count = 0;
        for (let i = left + 1; i < right; i++) {
            if (height[i] >= h) {
                count++;
            }
        }
        totalWater += currentWidth - count;
        h++;
    }
    return totalWater;
}

console.log(trap([0,1,0,2,1,0,1,3,2,1,2,1])); //  6
console.log(trap([4,2,0,3,2,5]));             //  9

问题分析

  1. 时间复杂度高 :对于最大高度为 H 的数组,时间复杂度为 O(H * n)。若 H 极大(如 1e5),效率极低。
  2. 重复扫描:每一层都需要重新扫描数组,导致大量冗余计算。

示例缺陷 :当输入为 [1e5, 0, 0, ..., 0, 1e5] 时,需要循环 1e5 层,性能无法接受。

所以在最后大量数据示例下运行超时。


三、双指针优化:O(n) 时间的智慧

这里就有夹逼的味道了,也有最短木板的想法。

优化后代码
javascript 复制代码
var trap = function(height) {
    let left = 0, right = height.length - 1;
    let leftMax = 0, rightMax = 0;
    let ans = 0;
    while (left < right) {
        leftMax = Math.max(leftMax, height[left]);
        rightMax = Math.max(rightMax, height[right]);
        if (height[left] < height[right]) {
            ans += leftMax - height[left];
            left++;
        } else {
            ans += rightMax - height[right];
            right--;
        }
    }
    return ans;
};
核心思路
  1. 双指针夹逼leftright 分别从数组两端向中间移动。
  2. 动态维护最大值
    • leftMax 记录左侧遍历过的最大高度。
    • rightMax 记录右侧遍历过的最大高度。
  3. 水位计算策略
    • 短板效应:当前水位由左右两侧较小的最大高度决定。
    • 单侧积累 :若 height[left] < height[right],则左指针处的水位由 leftMax 决定,反之同理。
正确性证明
  • 局部最优性:每一步移动指针时,总是先处理高度较低的一侧,确保该侧的水位不会超过当前已知的较小最大值。
  • 全局覆盖性:双指针遍历过程中,每个柱子的水位都被其左右两侧的最大值精确覆盖。

四、关键步骤拆解(以示例 1 为例)

ini 复制代码
初始状态:
left=0, right=11, leftMax=0, rightMax=0, ans=0

Step 1:
height[0]=0 < height[11]=1 → 左指针移动
ans += 0 - 0 = 0 → ans=0
left=1

Step 2:
leftMax=1(height[1]=1)
height[1]=1 < height[11]=1 → 移动左指针
ans += 1 - 1 = 0 → ans=0
left=2

...(中间步骤省略)

最终累计 ans=6

五、复杂度分析

  • 时间复杂度:O(n),仅需一次遍历。
  • 空间复杂度:O(1),仅需常数空间。

六、方法对比与适用场景

方法 时间复杂度 适用场景 优势
分层双指针法 O(H * n) 高度较低且分布均匀 思路直观,易于理解
优化双指针法 O(n) 任意高度分布 高效,处理大规模数据

七、总结

优化后的双指针法通过动态维护左右最大值,将问题转化为单次遍历的线性操作,完美解决了分层法的性能瓶颈。其核心在于利用短板效应贪心策略,以最小的时间复杂度完成计算。理解这一算法,不仅能够高效解决接雨水问题,更能深入掌握双指针与动态规划的联合应用。

相关推荐
VillanelleS2 分钟前
前端工程化之自动化部署
运维·前端·自动化
stoneSkySpace11 分钟前
算法—冒泡排序—js(教学示例、小数据)
java·javascript·算法
moyu8411 分钟前
高效开发必备:手把手整合IconFont、 Vant与Element Plus
前端·javascript
李帅朋11 分钟前
选择排序(简单选择排序、堆排序)
数据结构·算法·排序算法
trust Tomorrow12 分钟前
JS案例-基于Proxy的响应式数据
前端·javascript·css·html
BillKu14 分钟前
Vue3 + TypeScript,关于item[key]的报错处理方法
前端·javascript·vue.js·typescript
妄念鹿16 分钟前
关于tailwindcssV4版本官方插件没有提示
前端
七月丶17 分钟前
💬 打造丝滑交互体验:用 prompts 优化你的 CLI 工具(gix 实战)
前端·后端·github
愤怒的糖葫芦21 分钟前
异步编程进阶:Generator 与 Async/Await
前端·javascript
不想说话的麋鹿26 分钟前
「项目实战」从0搭建NestJS后端服务(八):静态资源访问以及文件上传
前端·node.js·全栈