Leetcode Hot 100 | 双指针
思想
今天下了一整天的雨,多么适合分析一下「解雨水」这道题及其方法——双指针。双指针的思路是,采用两个指针分别记录两个不同的位置,从而完成题目。常见的情景有:
- 快慢指针。在链表中讨论;
- 相向滑动指针。如解雨水和最大接水容器;
- 同向滑动窗口。在滑动窗口专题讨论。
题目
11. Container With Most Water
这道题给了我们一堆间隔为 1、高度不同的柱子,要求找出哪两个柱子之间能盛放最多的水。
1 | |
这道题可以反映短板效应。一个容器的容量的计算公式为:
$$
Volume = min(Hleft, Hright) * Width
$$
因此一开始让宽度最大,确保 $Width$ 变量已经做到了极限。在这个情况下,根据上述公式,得到一个结果。
接下来该移动边界了,哪边要缩小?根据公式分析,如果高板一侧缩小,那么,新容器的宽度减小、且高度肯定比现在低。说明什么?
如果挪动高板,肯定不会得到更好的解了。
那么,挪动短板一侧的界限,或许能得到更高的板,使得原来容器的高板成为短板,从而新容器的容量更大。
有人说,那,我就一直挪动高板一侧的边界,会不会万一有转机?
你指的是什么样的转机?如果得到更短的板,肯定不是转机。但就算得到更高的板,容器的容量还是被短板限制。所以必须挪动短板!补短板很重要。
15. 3Sum
这题要求我们找出数组中所有 unique 的三个数,s.t. 这三个数的和为 0。
1 | |
这题是怎么用双指针的呢?其实这道题是两数之和的拓展:先固定一个数,剩下的两个数作为两数之和来考虑。而在有序数组中,可以用两个指针来分别指向最大数和最小数。
复盘上面算法的逻辑。首先,考虑每个数作为定点的情况,为了防止重复考虑,left 和 right 只考虑这个点后续的数。
这道题的难点在于重复的处理。为了方便处理,先排序。对于定点,如果它前序和它相等,则不考虑当前点,因为之前已经考虑过相同的数。
而当找到一组解后呢?先加到返回向量中,再收缩边界:对于左侧边界,如果下一位和这一位相同,则收缩边界,右侧同理。最后,要再收缩一次。因为上面 while 退出时,边界指向的仍然是当前值,已经被考虑过了。
42. Trapping Rain Water
滴滴答、雨在下、今天都没停过呀。
1 | |
这道题的思路和最大容器的类似。核心的思路是:一个坑,或者一个位置最多能接多少水呢?取决于 $min(左边最高, 右边最高) - 当前高度$。这个道理是显然的。
那么该如何考虑这件事呢?我们需要为每个元素遍历左边最高和右边最高,逐个考虑吗?复杂度太高了。
按这个思路来,还是从最边界的情况开始。现在,有了 left_max 和 right_max,问你,该收缩哪里的边界?还是矮的那一侧。为什么?
分析一下,如果左边更矮,那么对于最左边的那根柱子,站在它的角度思考,能得到什么呢?
柱子想:我左边最高也就是 left_max 了(因为已经考虑过它左边的所有位置),右边还未考虑的那些柱子可能比 right_max 高,也可能更低,但不管怎样,最大值就由 left_max 定了。
所以,最左侧的位置可以下结论了,left++。而对右侧的柱子却不能下结论。右侧的柱子想:现在左边的最大高度比右边矮,但,左边还有一些柱子(left 和 right 之间的)没考虑,它们可能比现在的左边最大值大,还有上升空间,让子弹再飞一会。
283. Move Zeros
这道题让我们把 0 挪到数组的最后。
1 | |
思想是这样的,一个慢指针始终指向最靠前的 0,一个快指针往后遍历。当快指针发现一个非 0 元素时,交换,把非 0 元素往数组的前方放置。
另一种思考方式是,这道题让我们把非 0 数挪到数组的前面来。这样的话,每当发现一个非 0 元素后,就和数组最靠前的 0 进行交换。结束的时候,所有非 0 元素肯定都聚集在数组靠前位置。
我觉得很巧妙。
