【Hot 100 刷题计划】 LeetCode 42. 接雨水 | C++ 动态规划与双指针题解

LeetCode 42. 接雨水 | C++ 动态规划与双指针双解法题解

📌 题目描述

题目级别:困难 (Hard)

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

  • 示例 1:
    输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
    输出:6

💡 解题思路:核心物理定律 (木桶效应)

接雨水问题的核心在于**"化整为零"。我们不要从整体去考虑能接多少水,而是把目光聚焦在每一根单独的柱子**上。

问:下标为 i 的这根柱子,它的正上方到底能积攒多少水?

答:根据"木桶效应",这取决于它左边所有柱子中的最高点 ,以及右边所有柱子中的最高点 。

具体公式为:

第 i 列的水量 = min(左边最高, 右边最高) - 当前柱子的高度
(如果算出来是负数,说明当前柱子比两边都高,存不了水,取 0 即可)


🚀 解法一:前后缀分解 / 动态规划 (空间 O(N))

既然每一列都需要知道"左边最高"和"右边最高",我们可以提前把它们算出来存进数组里。

  1. 从左往右遍历一次,生成 left_max 数组,记录每个位置及其左边的最高高度。
  2. 从右往左遍历一次,生成 right_max 数组,记录每个位置及其右边的最高高度。
  3. 最后再遍历一次原数组,利用上面的公式计算水量并累加。

💻 C++ 代码实现 (标准规范版)

cpp 复制代码
class Solution {
public:
    int trap(vector<int>& height) {
        int n = height.size();
        if (n == 0) return 0;

        // 规范做法:使用 vector 代替变长数组
        vector<int> left_max(n);
        vector<int> right_max(n);

        // 1. 预处理左侧最大值数组
        left_max[0] = height[0];
        for (int i = 1; i < n; i++) {
            left_max[i] = max(left_max[i - 1], height[i]);
        }

        // 2. 预处理右侧最大值数组
        right_max[n - 1] = height[n - 1];
        for (int i = n - 2; i >= 0; i--) {
            right_max[i] = max(right_max[i + 1], height[i]);
        }

        // 3. 计算总积水量
        int res = 0;
        for (int i = 0; i < n; i++) {
            res += min(left_max[i], right_max[i]) - height[i];
        }

        return res;
    }
};

🏆 解法二:双指针 (时间 O(N),空间 O(1) 面试终极解)

解法一虽然快,但占用了额外的数组空间。能不能用 O(1)O(1)O(1) 的空间解决?答案是双指针。

我们在数组两端分别放置指针 left 和 right,同时用两个变量 l_max 和 r_max 来记录左右两边见过的最高高度。

既然决定水量的木板是短板,那么:

  • 如果 lmax<rmaxl_{max} < r_{max}lmax<rmax,说明左边这块板子更短。虽然我们不知道 right 指针以右还有没有更高的板子,但水桶的高度已经被左边的 lmaxl_{max}lmax 锁死了。此时我们可以放心地结算 left 指针所在列的水量,并将 left 向右移。
  • 反之,如果 lmax>=rmaxl_{max} >= r_{max}lmax>=rmax,说明右边是短板,高度被 rmaxr_{max}rmax 锁死。我们结算 right 指针所在列的水量,并将 right 向左移。

💻 进阶 C++ 代码实现

cpp 复制代码
class Solution {
public:
    int trap(vector<int>& height) {
        int n = height.size();
        if (n == 0) return 0;

        int left = 0, right = n - 1;
        int l_max = 0, r_max = 0;
        int res = 0;

        while (left < right) {
            // 更新左右两端的历史最高点
            l_max = max(l_max, height[left]);
            r_max = max(r_max, height[right]);

            // 谁是短板,谁就决定了当前列的水量,并向中间移动
            if (l_max < r_max) {
                // 左侧是短板,结算 left 处的水量
                res += l_max - height[left];
                left++;
            } else {
                // 右侧是短板,结算 right 处的水量
                res += r_max - height[right];
                right--;
            }
        }

        return res;
    }
};
相关推荐
文丑颜不良啊几秒前
LeetCode2161~2180题目解析
java·leetcode
雪落漂泊10 分钟前
C++算法 二分查找篇(上)
开发语言·c++·算法
不会算法的笨小孩2 小时前
异常的使用以及会出现的问题
开发语言·c++·c++11·异常
文丑颜不良啊2 小时前
LeetCode2141~2160题目解析
java·leetcode
LabVIEW开发2 小时前
LabVIEW 连接板:28 个端子怎么排,优先级怎么定
java·算法·labview
严肃小白兔3 小时前
MATLAB 数值计算中的精度与误差问题:浮点运算、误差传播与数值稳定性
人工智能·算法·matlab
Hhy_11074 小时前
《C++深度解构06》手撕 string —— 从底层内存管理到深浅拷贝彻底吃透
c语言·开发语言·c++·学习·visual studio
青少儿编程课堂4 小时前
蓝桥杯青少组图形化省考报名倒计时·全国青少年信息素养大赛·电子学会图形化考级:校园黑白棋人机对战解析
c++·python·算法·bfs·信息学竞赛
YSL0701244 小时前
C++基础
开发语言·c++
eggwyw5 小时前
关于C++为什么不加入垃圾回收机制解析
开发语言·c++