leetcode:42. 接雨水

题目来源

题目描述

在这里插入图片描述

class Solution {
public:
    int trap(vector<int>& height) {

    }
};

题目解析

关键思路

  • 对于每个下标 i i i, 分别计算它能够接到的水量
  • 然后把所有的水加起来就是最终答案
  • 一个位置要想接到雨水, 那么两边必然要有比它更高的柱子, 并且下雨后水能到达的最大高度等于下标 i 两边的最大高度的最小值, 而下标 i 处能接的雨水量等于下标 i 处的水能到达的最大高度减去 height[i].

在这里插入图片描述

关键在于,针对 n u m s [ i ] nums[i] nums[i]

  • n u m s [ 0... i − 1 ] nums[0...i-1] nums[0...i1]中找出最大的那个
  • n u m s [ i + 1... N − 1 ] nums[i+1...N-1] nums[i+1...N1]中找出最大的那个

暴力

对于数组中的每个元素,我们找出下雨后水能达到的最高位置

class Solution {
public:
    int trap(vector<int>& height) {
        int ans = 0;
        int N = height.size();

        for (int i = 1; i < N - 1; ++i) {
            int max_left = 0, max_right = 0;
            for (int j = i; j >= 0; --j) {
                max_left = std::max(max_left, height[j]);
            }
            for (int j = i; j < N; ++j) {
                max_right = std::max(max_right, height[j]);
            }
            ans += std::min(max_left, max_right) - height[i];
        }
        
        return ans;
    }
};

在这里插入图片描述

动态编程

在暴力方法中,我们仅仅为了找到最大值每次都要向左和向右扫描一次。但是我们可以提前存储这个值。

class Solution {
public:
    int trap(vector<int>& height) {
  
        int N = height.size();
        
        std::vector<int> left_max(N), right_max(N);
        left_max[0] = height[0];
        for (int i = 1; i < N; ++i) {
            left_max[i] = std::max( height[i], left_max[i - 1]);
        }
        right_max[N - 1] = height[N - 1];
        for (int i = N - 2; i >= 0; --i) {
            right_max[i] = std::max(height[i], right_max[i + 1]);
        }

        int ans = 0;
        for (int i = 1; i <= N - 2; ++i) {
            ans += std::min(left_max[i], right_max[i]) - height[i];
        }
        return ans;
    }
};

在这里插入图片描述

双指针

上面的方法,对每个位置都找到了两边的最大值,实际上,接水量是由两者中的较小值决定的,所以只要找到较少的那个即可。所以我们可以用双指针来进行优化

从上面的动态规划方程中,也可以看出。我们可以把:

ans += std::min(left_max[i], right_max[i]) - height[i];

优化成:

res += Math.min(leftMax, rightMax) - height[i];

注意这里的 leftMax 是从左端开始递推得到的,而 rightMax 是从右端开始递推得到的。

因此遍历每个柱子,累加每个柱子的储水高度时,也需要用 left 和 right 两个指针从两端开始遍历。

class Solution {
public:
    int trap(vector<int>& arr) {
        int N = arr.size();
        if(N < 2){
            return 0;
        }
        
        int L = 1,  R = N - 2;
        int leftMax = arr[0], rightMax = arr[N - 1];
        int water = 0;
        while (L <= R) {
            if (leftMax <= rightMax) {  //谁小谁结算答案(瓶颈)
                water += std::max(0, leftMax - arr[L]); // 是否形成凹槽
                leftMax = std::max(leftMax, arr[L++]);  //更新max
            } else {
                water += std::max(0, rightMax - arr[R]);
                rightMax = std::max(rightMax, arr[R--]);
            }
        }
        return water;
    }
};

还可以换种写法:

  • 谁小计算谁
  • 相等就一起结算

单调栈

如果当前柱子如果>=栈顶元素,说明形不成凹槽,则将当前柱子入栈;反之若当前柱子>栈顶元素,说明形成了凹槽,于是将栈中小于当前柱子的元素pop出来,将凹槽的大小累加到结果中。

维护一个单调栈,单调栈中存储的是下标,满足从下到上对应的数组height中的元素递减。

  • 从左到右遍历数组
  • 当前遍历到下标 i i i
    • 如果栈中至少有两个元素
      • 栈顶为top,top下面的元素是left,那么一定有 h e i g h t [ t o p ] < = h e i g h t [ l e f t ] height[top] <= height[left] height[top]<=height[left]
      • 如果 h e i g h t [ t o p ] < h e i g h t [ i ] height[top] < height[i] height[top]<height[i],则得到一个可以接雨水的区域
        • 该区域的宽度是 i − l e f t − 1 i−left−1 ileft1
        • 高度是 m i n ( h e i g h t [ l e f t ] , h e i g h t [ i ] ) − h e i g h t [ t o p ] min(height[left],height[i])−height[top] min(height[left],height[i])height[top]
        • 根据宽度和高度即可计算得到该区域能接的雨水量。
      • h e i g h t [ t o p ] height[top] height[top]答案结算完毕了,将 t o p top top出栈,然后left就变成了新的 t o p top top,重复上面操作,直到栈为空,或者 h e i g h t [ i ] > h e i g h t [ t o p ] height[i] > height[top] height[i]>height[top]。然后将 i i i入栈
class Solution {
public:
    int trap(vector<int>& arr) {
        int ans = 0;
        std::stack<int> stk;
        int N = arr.size();
        for (int i = 0; i < N; ++i) {
            while (!stk.empty() && arr[i] > arr[stk.top()]){
                int top = stk.top(); 
                stk.pop();
                if(!stk.empty()){
                    break;
                }
                int left = stk.top();
                int currWidth = i - left - 1;
                int currHeight = std::min(arr[left], arr[i]) - arr[top];
                ans += currHeight * currWidth;
            }   
            stk.push(i);
        }
        return ans;
    }
};

在这里插入图片描述

类似题目

题目思路
leetcode:11. 盛最多水的容器 Container With Most Water双指针
leetcode:42. 接雨水 Trapping Rain Water 对每个位置都找到了两边的最大值,实际上,接水量是由两者中的较小值决定的,所以只要找到较少的那个即可。所以我们可以用双指针来进行优化
leetcode:407. 接雨水 Trapping Rain Water II最小堆。首先格子能存水的前提:不是边界&&边界的最低也比自己高。因此我们先将边界入堆,然后找到最薄弱(出队时维护一个max,因为出队不一定是最薄弱的)的侵染它的四周(灌水)
leetcode:238. 除自身以外数组的乘积(不要用除法) Product of Array Except Self前缀和,s1[i] = num[0…i-1],s2[i] = num[i+1…n-1],最后ans[i] = s1[i] * s2[i]
755. 倒水
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
给定一个整数数组 nums 和一个目标值 target,要求在数组中找出两个数的和等于目标值,并返回这两个数的索引。 思路1:暴力法 最简单的思路是使用两层循环遍历数组的所有组合,判断两个数的和是否等于目标值。如果等于目标值,则返回这两个数的索引。 此方法的时间复杂度为O(n^2),空间复杂度为O(1)。 思路2:哈希表 为了优化时间复杂度,可以使用哈希表来存储数组中的元素和对应的索引。遍历数组,对于每个元素nums[i],我们可以通过计算target - nums[i]的值,查找哈希表中是否存在这个差值。 如果存在,则说明找到了两个数的和等于目标值,返回它们的索引。如果不存在,将当前元素nums[i]和它的索引存入哈希表中。 此方法的时间复杂度为O(n),空间复杂度为O(n)。 思路3:双指针 如果数组已经排序,可以使用双指针的方法来求解。假设数组从小到大排序,定义左指针left指向数组的第一个元素,右指针right指向数组的最后一个元素。 如果当前两个指针指向的数的和等于目标值,则返回它们的索引。如果和小于目标值,则将左指针右移一位,使得和增大;如果和大于目标值,则将右指针左移一位,使得和减小。 继续移动指针,直到找到两个数的和等于目标值或者左指针超过了右指针。 此方法的时间复杂度为O(nlogn),空间复杂度为O(1)。 以上三种方法都可以解决问题,选择合适的方法取决于具体的应用场景和要求。如果数组规模较小并且不需要考虑额外的空间使用,则暴力法是最简单的方法。如果数组较大或者需要优化时间复杂度,则哈希表或双指针方法更合适。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值