[每日一题]114:接雨水


题目描述

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

示例 1:

在这里插入图片描述

输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 

示例 2:

输入:height = [4,2,0,3,2,5]
输出:9

题解思路

方法一:暴力

对于数组中的每个元素,我们找出下雨后水能达到的最高位置,等于两边最大高度的较小值减去当前高度的值。

  1. 初始化 ans=0
  2. 从左向右扫描数组:
    初始化 max_left=0 和 max_right=0
    1. 从当前元素向左扫描并更新:
      max_left=max(max_left,height[j])
    2. 从当前元素向右扫描并更新:
      max_right=max(max_right,height[j])
    3. 将 min(max_left,max_right)−height[i] 累加到 res

复杂性分析

  • 时间复杂度: O(n^2)。数组中的每个元素都需要向左向右扫描。
  • 空间复杂度 O(1) 的额外空间。

代码实现:

class Solution {
    
    
public:
    int trap(vector<int>& height) {
    
    
        int res = 0, sz = height.size();
        for (int i = 1; i < sz - 1; ++i) {
    
    
            int max_left = 0, max_right = 0;
            for (int j = i; j >= 0; --j) {
    
    
                max_left = max(height[j], max_left);
            }
            for (int j = i; j < sz; ++j) {
    
    
                max_right = max(height[j], max_right);
            }
            res += min(max_left, max_right) - height[i];
        }
        return res;
    }
};

方法二:动态编程(方法一的优化)

  • 找到数组中从下标 i 到最左端最高的条形块高度 left_max。
  • 找到数组中从下标 i 到最右端最高的条形块高度 right_max。
  • 扫描数组 height 并更新答案:
    累加 min(max_left[i],max_right[i])−height[i] 到 res 上

复杂性分析

  • 时间复杂度:O(n)。
    存储最大高度数组,需要两次遍历,每次 O(n) 。
    最终使用存储的数据更新 res ,O(n)。
  • 空间复杂度:O(n) 额外空间。
    和方法 1 相比使用了额外的 O(n) 空间用来放置 left_max 和 right_max 数组。

代码实现:

class Solution {
    
    
public:
    int trap(vector<int>& height) {
    
    
        int res = 0, sz = height.size();
        if (sz == 0) return 0;

        vector<int> max_left(sz), max_right(sz);
        max_left[0] = height[0];
        for (int i = 1; i < sz ; ++i) {
    
    
            max_left[i] = max(height[i], max_left[i-1]);
        }
        max_right[sz-1] = height[sz-1];
        for (int i = sz - 2; i >= 0; --i) {
    
    
            max_right[i] = max(height[i], max_right[i+1]);
        }
        for (int i = 1; i < sz - 1; ++i) {
    
    
            res += min(max_left[i], max_right[i]) - height[i];
        }
        return res;
    }
};

方法三:栈

在遍历数组时维护一个栈。如果当前的条形块小于或等于栈顶的条形块,我们将条形块的索引入栈,意思是当前的条形块被栈中的前一个条形块界定。如果我们发现一个条形块长于栈顶,我们可以确定栈顶的条形块被当前条形块和栈的前一个条形块界定,因此我们可以弹出栈顶元素并且累加答案到 res 。

  • 使用栈来存储条形块的索引下标。
  • 遍历数组:
    • 当栈非空且 height[cur]>height[st.top()]
      • 意味着栈中元素可以被弹出。弹出栈顶元素 top。
      • 计算当前元素和栈顶元素的距离,准备进行填充操作
        distance=cur−st.top()−1
      • 找出界定高度
        bounded_height=min(height[current],height[st.top()])−height[top]
      • 往答案中累加积水量 res+=distance×bounded_height
    • 将当前索引下标入栈
    • 将 cur 移动到下个位置

复杂性分析: aa

  • 时间复杂度:O(n)。
    单次遍历 O(n) ,每个条形块最多访问两次(由于栈的弹入和弹出),并且弹入和弹出栈都是 O(1) 的。
  • 空间复杂度:O(n)。 栈最多在阶梯型或平坦型条形块结构中占用 O(n) 的空间。

代码实现:

class Solution {
    
    
public:
    int trap(vector<int>& height) {
    
    
        int res = 0, cur = 0;
        stack<int> st;
        
        while (cur < height.size()) {
    
    
            while (!st.empty() && height[cur] > height[st.top()]) {
    
    
                int top = st.top();
                st.pop();
                if (st.empty()) break;
                int distance = cur - st.top() - 1;
                int bounded_height = min(height[cur], height[st.top()]) - height[top];
                res += distance * bounded_height;
            }
            st.push(cur++);
        }
        return res;
    }
};

方法四:双指针法

从动态编程方法的示意图中我们注意到,只要 right_max[i]>left_max[i] (元素 0 到元素 6),积水高度将由 left_max 决定,类似地 left_max[i]>right_max[i](元素 8 到元素 11)。
所以我们可以认为如果一端有更高的条形块(例如右端),积水的高度依赖于当前方向的高度(从左到右)。当我们发现另一侧(右侧)的条形块高度不是最高的,我们则开始从相反的方向遍历(从右到左)。
我们必须在遍历时维护 left_max 和 right_max ,但是我们现在可以使用两个指针交替进行,实现 1 次遍历即可完成。

复杂性分析:

  • 时间复杂度:O(n)。单次遍历的时间 O(n)。
  • 空间复杂度:O(1) 的额外空间。left, right, left_max 和 right_max 只需要常数的空间。

代码实现:

class Solution {
    
    
public:
    int trap(vector<int>& height) {
    
    
        int left = 0, right = height.size() - 1;
        int ans = 0;
        int left_max = 0, right_max = 0;
        
        while (left < right) {
    
    
            if (height[left] < height[right]) {
    
    
                height[left] >= left_max ? (left_max = height[left]) : ans += (left_max - height[left]);
                ++left;
            }
            else {
    
    
                height[right] >= right_max ? (right_max = height[right]) : ans += (right_max - height[right]);
                --right;
            }
        }
        return ans;
    }
};

猜你喜欢

转载自blog.csdn.net/AngelDg/article/details/114519700