题外话
这个算法是我参考的网上的一个讲解大神叫Edward Shi 的,觉得非常巧妙,所以特意分享在这里,o(1)的空间复杂度, o(n)的时间复杂度。
非常巧妙的two pointer算法,不过我仔细想了一下,觉得这么巧妙的算法如果不是以前做过,真的在面试当中第一次看到,应该临时想不出这种算法,所以我觉得wzc1995的算法1非常的重要,因为它相当于是一种过渡,就是你当下看到可以立马想到也可以在短时间内写出来的算法,而且非常符合人的思维方式,刷了很多题之后,觉得计算机的思维方式跟人来说有很大差别,乍一看可能还会觉得,诶,为什么这样就行得通呢?
然后我看了wzc1995的算法一之后,再来看这个算法,就会明白,哦,原来是因为这样啊。
算法
(two pointer) $O(n)$
一般情况下,如果按照人的思维方式的话,要看每一个位置可以蓄多少水,我们首先会看看,左边最高的柱子有多高(这里的左边最高还包括当前这个位置的,不是真的就是在左边的最高的柱子),右边的柱子最高的有多高,然后取他们两个当中的最小值,减去当前柱子的高度,就是这个位置可以蓄的水的量了。
为什么取两边的最高的柱子当中的最小值呢?因为一个桶能蓄多少水看短板(这里我们有两个板,一个长板,一个短板)对不对。wzc1995的算法一就是把左边的最高的柱子找出来,把右边的最高的柱子也找出来,然后再取最小值,就算是找到了短板了。
而这个two pointer的算法就是,为什么非要把两边的最高的柱子都找出来再找短板呢?我们直接找短板不就好了。
为什么这样行得通呢?
因为你想啊,左边一个值记录到当前位置为止左边最高的柱子,右边一个值记录到当前位置为止右边最高的柱子,假设我们知道右边这个最高的柱子比左边那个最高的柱子矮,那么在当前位置,这个比较矮的右边最高柱一定是当前位置的短板,为什么呢?因为这个右边最高柱已经比那个左边最高柱矮了,那么即便两个
pointer 中间还有没有走完的位置,当前位置的长板也大于等于那个没有走完的左边最高柱了。所以我们就证明了,这个比较矮的板是当前位置的短板,然后用短板值减去当前柱子的高度就是当前位置蓄水量了。
整个vector扫一遍就是o(n)时间复杂度,左边一个pointer,右边一个pointer,还有两个值分别记录左边最高柱、右边最高柱,还有一个值记录蓄水量。所以是o(1)空间复杂度。
时间复杂度分析:o(n)
C++ 代码
class Solution {
public:
int trap(vector<int>& height) {
int n = height.size();
if (n == 0) {
return 0;
}
int l = 0, r = n - 1;
int leftmost = height[l], rightmost = height[r];
int res = 0;
while (l <= r) {
if (leftmost < rightmost) {
res += leftmost - height[l++];
leftmost = max(leftmost, height[l]);
} else {
res += rightmost - height[r--];
rightmost = max(rightmost, height[r]);
}
}
return res;
}
};
妙啊~
我再来总结一下:先看那种三重遍历的做法。然后想是不是可以优化一点。双指针法(O(N)确实只需要一次遍历),分别记录左右两边的柱子的最大值left_max和right_max(空间开销也变小)。思维量就在于不必非得提前记录所有柱子左右两边的柱子最大值的较小值,而是只需要一边遍历一边比较现在双指针指向的当前柱子的左右两边的最大值,省去了两遍的遍历时间。当遍历当前柱子height[i]时,假设找到当前柱子的高度比min(left_max,right_max)还要小的情况,就是能存下水,当前柱子积水的高度就取决于min(left_max,right_max)-height[i],不需要担心其余的柱子的高或者低,因为总有左边或右边高的那个柱子存住水,这样子就可以忽略其他柱子高度的影响,简化了思维,只需要关心当前柱子,左边最高的那个珠子,右边最高的那个柱子。
<=打错了
抱歉抱歉,没仔细看名字,太不好意思了。
这个算法很赞!
不过这个题目的题解是@wzc1995写的,不是我hh