LeetCode 904 水果成篮
题目链接:
题意解析:
本题是同向双指针中滑动窗口解法的应用,题意为给定 fruits 数组,每种数字代表一种水果,拥有两个篮子,每个篮子只能放一种采摘水果,要求采摘必须连续,求可以收集水果的最大数目。抽象一下题意可以表述为在给定的 int 类型的数组中,找到该数组的最长连续子数组,但是要求子数组内至多两种数字。
思路:
-
暴力解法:暴力的一个想法就是一层 for 循环控制采摘点的起始位置,因为题目要求一旦开始采摘就不能停下来(连续性),所以还需要一层 for 循环控制采摘点的结束位置,结束的条件就是收集了两种以上的水果。结果自然是一个 O(n^2) 的时间复杂度。
-
双指针:这里我们可以尝试使用一个 for 循环完成滑动窗口的动态扩充与缩减,需要用到快慢指针。快指针只负责向右扩充滑动窗口大小,慢指针只负责向右缩减滑动窗口大小。这里慢指针缩减窗口是需要一定的条件的,而快指针扩充窗口的条件却相对而言没有那么苛刻,因此考虑将快指针 fast 放入 for 循环借以控制移动,每次快指针移动就将新采摘的水果放入篮子 Map 中,数量++。接下来就是慢指针缩减窗口的条件,那就是当水果种类数量大于 2 时(map.size() > 2)就需要进入移动慢指针的环节了,而且因为考虑需要多次动态缩减,所以使用 while 循环控制。这里就采取与扩充相反的操作,把慢指针 slow 所到处的水果都踢出篮子 Map,数量--,而且一旦发现某个水果数量为 0 了(map.get(fruits[slow]) == 0)就把这个水果种类也彻底从 Map 中铲除掉(相当于 size--),维护动态窗口的合法性,避免陷入死循环。那在缩减窗口的 while 逻辑之后就是实时更新滑动窗口的历史最大长度(收集水果的最大数目),此刻的窗口一定是合法的。
总结:
- 快指针是导致滑动窗口合法性丧失的罪魁祸首
- 慢指针是使得滑动窗口重新合乎条件的帮手
- 快慢指针共同维护一个有条件的滑动窗口
LeetCode 76 最小覆盖子串
题目链接:
题意解析:
本题依旧是滑动窗口的应用,题意为给定两个字符串 s 和 t,要求返回 s 的一个最长子串,但要求这个子串包含 t 中每一个字符(包含关系即可,顺序无要求,不是每一种字符,因此需要考虑每种字符的数量也要一致)。
思路:
-
**暴力解法:**一层 for 循环控制 s 字符串的子串的起点,另一层 for 循环控制终点,在维护子串合法性的前提下,实时更新子串为最大长度。
-
滑动窗口:快指针 right 负责扩充窗口长度,for 循环中不断向右遍历,慢指针 left 负责缩减窗口长度,while 循环控制窗口非法性,循环内尝试更新 ansLeft 和 ansRight 保证最终窗口尽可能地长。前置操作设置两个长度为 128 的数组哈希 cntS 和 cntT,分别记录字符串 s 的子串窗口和字符串 t 中每种字符的数量。while 循环条件是由一个自定义方法 isCovered 负责判断,这个条件存在的根本原因是当当前窗口无法覆盖字符串 t 的所有字符时,为了通过慢指针循环移动使得窗口重新合法。而之前的数组哈希就是在此时派上用场了,isCovered 方法里面本质上就是遍历窗口和字符串 t 的每个字符,比较数量,一旦发现窗口中有某个字符数量小于字符串 t 中的某个字符,那就说明覆盖条件无法达成直接 return false,快指针继续遍历字符串 s,扩大窗口,让窗口重新覆盖字符串 t。如果覆盖条件达成,则继续 while 循环,慢指针右移,窗口中字符不断减少,试图'破坏'覆盖条件。

