接雨水

跳转原题

解法

思考水是怎么被接住的?其实就是左右较高的柱子围住中间较矮的柱子,那么中间的水就被接住了,也就是说左右的高度决定了水的上限,难点在于下限的凹凸不平,这时候不妨考虑按列求解,仔细观察题图不难发现每一列的水只由左、右两边最高的那两列和自己的高度决定,他们的差值就是这一列接住的水。

解法一: 暴力

实现代码很容易想到外层枚举每一列,内层向左右查找当前这一列对应的左右的最高列然后每一列都把答案添加到ans,代码如下:

点击查看代码
class Solution {
   public:
    int trap(vector<int>& height) {
        int ans = 0;
        //首尾柱子不可能接水
        for (int i = 1; i < height.size() - 1; ++i) {
            int highL = 0, highR = 0;
            for (int j = i - 1; j >= 0; --j) {
                if (height[j] > highL) {
                    highL = height[j];
                }
            }
            for (int j = i + 1; j < height.size(); ++j) {
                if (height[j] > highR) {
                    highR = height[j];
                }
            }
            if (height[i] >= highL || height[i] >= highR) {
                continue;
            } else {
                ans += min(highL, highR) - height[i];
            }
        }
        return ans;
    }
};
  • 时间复杂度: O(n2)
  • 空间复杂度:O(1)

但是这样O(n2)就爆了(lc貌似没有时间限制/空间限制tip),所以可以用预处理优化。

解法二:预处理

先用两层单独的循环预处理两个数组:当前位置i左右的当前最高柱子maxL/maxR,然后遍历列的时候直接取。代码如下:

点击查看代码
class Solution {
   public:
    int trap(vector<int>& height) {
        int ans = 0;
        int n = height.size();
        vector<int> maxL(n);
        vector<int> maxR(n);
        for (int i = 1; i < n - 1; ++i) {
            maxL[i] = max(maxL[i - 1], height[i - 1]);
        }
        for (int i = n - 2; i >= 0; --i) {
            maxR[i] = max(maxR[i + 1], height[i + 1]);
        }
        for (int i = 1; i < n - 1; ++i) {
            if (min(maxL[i], maxR[i]) > height[i]) ans += min(maxL[i], maxR[i]) - height[i];
        }
        return ans;
    }
};
  • 时间复杂度: O(n)
  • 空间复杂度:O(n)

然后你就会发现两个数组完全也没必要,因为处理完当前位置(i)后就用不到位置对应的左右柱子最大值了。

解法三:双指针

可以初始化两个指针,放在头尾分别向前和向后遍历维护当前的ans,代码如下:

点击查看代码
class Solution {
public:
    int trap(vector<int>& height) {
        int ans = 0;
        int n = height.size();
        int maxL = 0, maxR = 0;
        // 注意这里不能把l设置在首位,r设置在末尾,可以理解为指针指向的是当前的可接水的列,l-1和r+1代表的是当前的新候选柱子index
        int l = 1, r = n - 2;

        for (int i = 1; i < n - 1; ++i) {
            // 从左到右:这时候左边最高应该<右边最高
            if (height[l - 1] < height[r + 1]) {
                // 枚举列的同时维护当前左指针最大值
                maxL = max(maxL, height[l - 1]);
                if (maxL > height[l]) {
                    ans += (maxL - height[l]);
                }
                l++;
            } else {
                maxR = max(maxR, height[r + 1]);
                if (maxR > height[r]) {
                    ans += (maxR - height[r]);
                }
                r--;
            }
        }
        return ans;
    }
};
  • 时间复杂度: O(n)
  • 空间复杂度:O(1)

解法四:单调栈

由于这道题的名气大于题本身,导致我在做之前就听说过是可以用栈做,这里直接进入实现思路吧。。。。

思路就是对每一堵墙,遍历墙的高度的时候,
如果当前高度小于栈顶的墙高度,说明这里会有积水,我们将墙的高度的下标入栈。
如果当前高度大于栈顶的墙的高度,说明之前的积水到这里停下,我们可以计算下有多少积水了。计算完,就把当前的墙继续入栈,作为新的积水的墙。

点击查看代码
class Solution {
public:
    int trap(vector<int>& height) {
        int ans = 0;
        int n = height.size();
        stack<int> st;
        int cur = 0;
        while (cur < n) {
            // 如果栈不空并且当前指向的高度大于栈顶高度就一直循环
            // 也就是保持在栈里的是一个单调递减的高度
            while (!st.empty() && height[cur] > height[st.top()]) {
                int h = height[st.top()];
                st.pop();
                // 如果出栈后栈为空,说明左边没有墙了,无法形成容器,跳出内层循环
                if (st.empty()) {
                    break;
                }
                int width = cur - st.top() - 1;
                int mnh = min(height[cur], height[st.top()]);
                ans += (mnh - h) * width;
            }
            st.push(cur);
            cur++;
        }
        return ans;
    }
};
  • 时间复杂度:虽然 while循环里套了一个 while 循环,但是考虑到每个元素最多访问两次,入栈一次和出栈一次,所以时间复杂度是 O(n)。

  • 空间复杂度O(n) 。栈的空间

posted @ 2025-09-28 11:48  Sunstreamy  阅读(6)  评论(0)    收藏  举报