当青训营遇上码上掘金
这题是一道很经典的动态规划题(接雨水:嗨呀 是我吗)
当我看到这道题的时候的第一想法:动态规划,仔细观察可以发现,每一处能接的雨水,取决于它左边的最高点和右边的最高点,哪个更低一点(比如 题目中给的第四列,它就取决于左边的最高点5以及右边的最高点4中的最低点4),这就是大名鼎鼎的木桶效应,那么这道题就可以很简单的转化为一个维护每一列左右两边最高点的值的动态规划题,
int n = height.length;
int[] dpl = new int[n];
int[] dpr = new int[n];
int lm = -1,rm = -1;
先定义出n的值与维护两边最高点的数组
for (int i = 0; i < n; i++) {
if (height[i] > lm) {
dpl[i] = height[i];
lm = dpl[i];
} else {
dpl[i] = lm;
}
}
接着先从左向右遍历一遍
for (int i = n-1; i >= 0; i--) {
if (height[i] > rm) {
dpr[i] = height[i];
rm = dpr[i];
} else {
dpr[i] = rm;
}
}
再从右往左遍历一遍,到这里,我们就得到了每一列的左右两端最大值
for (int i = 1; i < n-1; i++) {
sum += Math.max((Math.min(dpl[i],dpr[i])-height[i]),0);
}
接着直接遍历,注意,第0列与最后一列无论如何都存不下青豆,可以直接忽略 这道题就这样简单的完成了,但我提交的时候发现,空间复杂度怎么这么高?回头review了一下写的代码,发现好像维护的数组,能用到的地方只有最后一段代码,而且三个for其实长度都差不多,那我是不是可以把他们合并到一起呢?注意到每一处能接水量只和左右最高点较小值决定,那么如果能知道每一个点的左右两边较小值与左右关系,其实就能算出总和,因此,可以考虑使用双指针,左右两边都向中间寻找
int n = height.length;
int sum = 0;
int left = 0,right = n-1;
int lm = -1,rm = -1;
先初始化双指针以及左右最大值
当两个指针没有相遇的时候如果height[left] < height[right]那么显然leftmax<rightmax ,那么直接加上rightmax-height[right],right继续左移,反之亦然,将left右移,当left与right相遇的时候,即可得到所有接到的雨水
代码如下
while (left < right) {
lm = Math.max(lm, height[left]);
rm = Math.max(rm, height[right]);
if (height[left] < height[right]) {
sum += lm - height[left];
++left;
} else {
sum += rm - height[right];
--right;
}
}
至此,我们就用双指针与dp两种方式做完了这题,可以看出,基本会用到dp的做法,空间复杂度都不可能低于o(n),所以当用到dp的时候可以试试能不能用其他做法来解决,下面是我的代码片段,trap1为方法1,trap2位改进后的放发2