【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;
    }
};
相关推荐
地平线开发者2 小时前
智能驾驶感知算法的演进
算法·自动驾驶
爱丽_2 小时前
B+ 树范围查询为什么快:页分裂/合并、索引设计与 SQL 写法优化
数据库·算法·哈希算法
企鹅的蚂蚁2 小时前
【ESP32-S3开发踩坑】C++野指针引发的LoadProhibited死机与CMake依赖锁死排查
开发语言·c++
WBluuue3 小时前
Codeforces 1087 Div2(ABCDEF)
c++·算法
Yzzz-F3 小时前
2025 ICPC武汉邀请赛 G [根号分治 容斥原理+DP]
算法
初圣魔门首席弟子3 小时前
1768. 交替合并字符串 详细题解
c++
abant23 小时前
leetcode 114 二叉树变链表
算法·leetcode·链表
tankeven3 小时前
HJ165 小红的优惠券
c++·算法
Jasmine_llq3 小时前
《B3840 [GESP202306 二级] 找素数》
开发语言·c++·试除法·顺序输入输出算法·素数判定算法·枚举遍历算法·布尔标记算法