力扣: 动态规划 “凹”形 接雨水

128 阅读5分钟

前言

我们之前呢,也分析过什么叫做动态规划,这里呢就不再旧事重提,不明白可以去我的主页下面看看上一篇的动态规划。不过这里还是要放上我们的动态规划四部曲

解题思想四部曲

1. 确定dp的状态 : 定义为一维二维还是三维?表示什么含义?

2. 确定状态转移方程

3. 初始化,也就是dp数组可以取得的元素的值

4. 自底向上的方式计算dp,并得出最优值

例题

来,我们直接开始吃大餐!

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

rainwatertrap.png

输入: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 个单位的雨水(蓝色部分表示雨水)。

思路分析

rainwatertrap(1).png

如上图所示。能储存雨水的其实就是一个槽一样的图形。意思也就是说,中间低两边高才可以存储雨水。我们就如上图所显示的第一个蓄水池的位置,来分析它的存储雨水量是如何计算的。那我们就从图形中找形。第一个蓄水池左边高度为height[1]为1,右边高度height[3]为2,形成了以height[1]为中心的第一个蓄水池,能储水的高度就为1,那么这个时候我们就猜想储水高度是否为两高度之差?那么我们来看k的位置,这里的情形可能不太一样,可能看不出这里哪里有凹槽,但是很明显得出的蓄水量不是我们之前的结论,而且能蓄两格水又是为什么?那我们看红色箭头可能就明白了:左边的height[2]与右边的height[6]形成了它的凹槽,那么可能看到这里你可能会惊呼一声,然后好像明白了什么。这个时候你可能会推测某位置的蓄水量是否为两边最大高度当中的最小高度?并且这个结论同样适用于第一个蓄水池。那么最后我们再来看看m,这个位置又有些许不同,这个凹槽储水的情况居然是上半部分。两个蓝色箭头所指的height[8]height[9]虽然形成了凹槽,但是蓄水量却为1,又推翻了我们之前的结论。那么这个时候你又开始了你的猜想,并且通过其他位置蓄水池的验证,你发现真相只有一个:某位置的蓄水量为两边最大高度当中的最小高度与当前位置的高度的差值。那么我们为了表示方便,我们就暂且定义maxLeftmaxRight,来分别表示其左边的与其右边的最大高度。那么这个i位置的蓄水量表达式就为min(height[maxLeft],hegiht[maxRight]) - height[i]

那么我们的思路就很清晰了,这里所谓的dp数组的含义也就是到某位置的蓄水量,但是此处我们却可以用一个常数来替代,来节省空间,因为我们这里很明显求和就可以解决。这里明显也需要变通一下,与以往的有点不同。

下一步就是我们的状态转移方程,那么我们就需要知道,我们一开始推导计算公式是为什么,就是为了让你知道我们接下来需要定义的状态是什么。很明显,这里我们需要递推的是maxLeftmaxRight的两个值,那么我们就可以写出两个状态转移方程:

maxLeft[i] = max(height[i], maxLeft[i - 1]);

maxRight[i] = max(height[i], maxRight[i + 1]);

初始化值那么就明显了,我们的maxLeft[0]应该就为height[0],maxRight[size-1]就为height[size - 1]

然后呢我们再思考它的计算方式,也就是遍历顺序,这道题目直接从前往后依次计算,然后结果求和即可,但是需要注意的是maxLeft为从前往后,而maxRight为从后往前。那么我们来看看具体实现代码。

具体代码

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

        // 记录每个柱子左边柱子最大高度
        maxLeft[0] = height[0];
        for (int i = 1; i < size; i++) {
            maxLeft[i] = max(height[i], maxLeft[i - 1]);
        }
        // 记录每个柱子右边柱子最大高度
        maxRight[size - 1] = height[size - 1];
        for (int i = size - 2; i >= 0; i--) {
            maxRight[i] = max(height[i], maxRight[i + 1]);
        }
        // 求和
        int sum = 0;
        for (int i = 0; i < size; i++) {
            int count = min(maxLeft[i], maxRight[i]) - height[i];
            if (count > 0) sum += count;
        }
        return sum;
    }
};

代码分析

这里为了记录每个位置的左右最大高度,我们采用了数组的方式存储,为了方便计算,我们定义了maxLeft[]``maxRight[]两个数组。初始化之后,便从1开始遍历。每次遍历时当前位置的高度都要与前一个最大高度的值进行比较,以此来得到最大高度。经过遍历后,每个位置都对应了左边最大高度与右边的最大高度,那么我们只需要通过 min(maxLeft[i], maxRight[i]) - height[i];来依次计算每个位置的储水量,相加即可。但是这里需要注意的是,只有计算出来的值大于0才是蓄水量,值小于0的其实就是这种模型。 然后再将这些值相加,就得到了结果。

总结

动态规划确实也是比较难,有的时候也会做到晕头转向,感觉这样可以,这样似乎也可以,找出状态转移方程也并非这么容易,难点也在于此。所以还是要自己多思考,多练习,多总结。

希望可以和大家一起学习,谢谢各位!