阅读视图

发现新文章,点击刷新页面。

【LeetCode 刷题系列 | 第 1 篇】前端老司机拆解接雨水问题,从暴力到最优解💦

🌧️ 前言

Hello~大家好。我是秋天的一阵风

欢迎来到我的 LeetCode 刷题系列专栏~ 作为一名深耕前端多年的老司机,我深知算法能力对前端工程师的重要性 —— 它不仅能帮我们在面试中脱颖而出,更能提升日常业务代码的逻辑严谨性和性能优化能力。

今天咱们要攻克的是 LeetCode 中的经典 hard 题「接雨水」,这道题堪称 “面试高频钉子户”,考察的核心是对数组遍历和边界判断的理解。

很多同学一开始会被它唬住,但只要咱们从基础思路慢慢拆解,再逐步优化,就能轻松拿捏!话不多说,咱们直奔主题~

一、LeetCode 接雨水题目详情

1. 题目描述

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

题目链接42. 接雨水 - 力扣(LeetCode)

2. 示例演示

image.png
  • 输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
  • 输出:6
  • 解释:如题目中的高度图所示,下雨后能接住 6 单位的雨水。
  • 输入:height = [4,2,0,3,2,5]
  • 输出:9

3. 难度级别

🔴 困难:这道题之所以被归为困难,是因为它需要突破常规的遍历思维,从 “局部最优” 推导 “全局最优”。但只要掌握了核心逻辑,它其实是一道 “纸老虎” 题~

二、解题思路大剖析

1. 暴力解法:直捣黄龙的基础思路

暴力解法的核心逻辑很朴素:逐个计算每个柱子能接的雨水量,最后求和。而一个柱子能接多少水,完全取决于它左右两侧的 “最高屏障”—— 也就是左右两侧最高柱子中较矮的那一个,用这个较矮值减去当前柱子的高度,就是该位置能接的水量(若结果为负,则接 0 水)。

核心步骤拆解:

  1. 遍历数组中的每一个柱子(索引从 0 到 n-1);
  1. 对当前柱子 i,向左遍历所有柱子,找到左侧的最高高度leftMax
  1. 对当前柱子 i,向右遍历所有柱子,找到右侧的最高高度 rightMax
  1. 计算当前柱子的接水量:Math.max(0, Math.min(leftMax, rightMax) - height[i]);
  1. 累加所有柱子的接水量,得到总水量。

分步拆解演示(以输入 [0,1,0,2,1,0,1,3,2,1,2,1] 为例):

image.png

咱们逐个扒开每个位置的接水逻辑,从索引 0 开始:

  • 索引 0:左侧没有柱子,leftMax=0;右侧最高柱子高度是 3。min(0,3)=0,接水量 0-0=0;
  • 索引 1:左侧最高是 0,右侧最高是 3。min(0,3)=0,接水量 0-1=-1,取 0;
  • 索引 2:左侧遍历 [0,1],最高是 1;右侧遍历 [2,1,0,1,3,2,1,2,1],最高是 3。min(1,3)=1,接水量 1-0=1;
  • 索引 3:左侧最高是 1,右侧最高是 3。min(1,3)=1,接水量 1-2=-1,取 0;
  • 索引 4:左侧最高是 2,右侧最高是 3。min(2,3)=2,接水量 2-1=1;
  • 索引 5:左侧最高是 2,右侧最高是 3。min(2,3)=2,接水量 2-0=2;
  • 索引 6:左侧最高是 2,右侧最高是 3。min(2,3)=2,接水量 2-1=1;
  • 索引 7:左侧最高是 2,右侧最高是 2。min(2,2)=2,接水量 2-2=0;
  • 索引 8:左侧最高是 3,右侧最高是 2。min(3,2)=2,接水量 2-2=0;
  • 索引 9:左侧最高是 3,右侧最高是 2。min(3,2)=2,接水量 2-1=1;
  • 索引 10:左侧最高是 3,右侧最高是 1。min(3,1)=1,接水量 1-2=-1,取 0;
  • 索引 11:右侧没有柱子,接水量 0;

把这些有效接水量相加:1+1+2+1+1=6,和示例输出一致!

JavaScript 代码实现(暴力解法):

/**
 * @param {number[]} height
 * @return {number}
 */
var trap = function(height) {
    const n = height.length;
    let total = 0; // 总接水量
    // 遍历每个柱子(除了首尾也可以,但不影响结果,代码更简洁)
    for (let i = 0; i < n; i++) {
        let leftMax = 0; // 左侧最高柱子高度
        let rightMax = 0; // 右侧最高柱子高度
        // 向左遍历,找左侧最高
        for (let j = i; j >= 0; j--) {
            leftMax = Math.max(leftMax, height[j]);
        }
        // 向右遍历,找右侧最高
        for (let j = i; j < n; j++) {
            rightMax = Math.max(rightMax, height[j]);
        }
        // 计算当前柱子的接水量,累加到总水量
        total += Math.min(leftMax, rightMax) - height[i];
    }
    return total;
};
// 测试用例
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

暴力解法的优缺点:

  • 优点:思路简单直观,容易理解,适合作为入门思路;
  • 缺点:时间复杂度极高,为 O (n²)(每个柱子都要左右遍历一次),当 n 较大时(比如 10^4)会直接超时,不适合实际面试场景。

2. 双指针解法:空间优化的最优思路

暴力解法的问题在于 “重复遍历”,双指针的核心是利用左右两侧的最大值关系,在一次遍历中完成计算,把时间复杂度降到 O (n),空间复杂度优化到 O (1)。

核心原理:

  • 定义左右指针 left(初始 0)和 right(初始 n-1);
  • 定义 leftMax(左侧已遍历的最高高度)和 rightMax(右侧已遍历的最高高度);
  • 当 height[left] <= height[right] 时,左侧的最大值 leftMax 决定了当前 left 位置的接水量(因为右侧有更高的柱子兜底);
  • 反之,右侧的最大值 rightMax 决定当前 right 位置的接水量;
  • 遍历过程中不断更新 leftMax、rightMax 和总水量,直到指针相遇。

JavaScript 代码实现(双指针解法):

/**
 * @param {number[]} height
 * @return {number}
 */
var trap = function(height) {
    const n = height.length;
    if (n < 3) return 0; // 少于3个柱子无法接水
    let left = 0, right = n - 1;
    let leftMax = 0, rightMax = 0;
    let total = 0;
    while (left < right) {
        // 左侧柱子更矮,以leftMax为基准
        if (height[left] <= height[right]) {
            if (height[left] >= leftMax) {
                leftMax = height[left]; // 更新左侧最高
            } else {
                total += leftMax - height[left]; // 计算当前位置接水量
            }
            left++; // 左指针右移
        } else {
            // 右侧柱子更矮,以rightMax为基准
            if (height[right] >= rightMax) {
                rightMax = height[right]; // 更新右侧最高
            } else {
                total += rightMax - height[right]; // 计算当前位置接水量
            }
            right--; // 右指针左移
        }
    }
    return total;
};
// 测试用例
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. 时间复杂度

  • 暴力解法:O (n²),双层循环导致重复遍历;
  • 双指针解法:O (n),一次遍历完成所有计算;

2. 空间复杂度

  • 暴力解法:O (1),仅使用常数级额外空间;
  • 双指针解法:O (1),同样仅使用常数级额外空间;

总结

好啦,今天的接雨水问题就讲到这里!相信大家已经对这道题的各种解法了如指掌。如果你有更优的思路,或者在刷题过程中遇到了疑问,欢迎在评论区留言讨论~

下一篇专栏,咱们将攻克另一道前端面试高频题,猜猜是什么?关注我,刷题不迷路!咱们下期再见~ 👋

❌