算法# 学习目标:双指针(二)
学习内容:
双指针:两个指针指向不同元素,从而协同完成任务,主要用于遍历元素。
学习产出:
对撞指针,快慢指针,滑动窗口
滑动窗口
滑动窗口法:可以用来解决一些查找满足一定条件的连续区间的性质(长度等)的问题。由于区间连续,因此当区间发生变化时,可以通过旧有的计算结果对搜索空间进行剪枝,这样便减少了重复计算,降低了时间复杂度。往往类似于“请找到满足xx的最x的区间(子串、子数组)的xx”这类问题都可以使用该方法进行解决。
LeetCode 76 最小覆盖子串
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。
注意:如果 s 中存在这样的子串,我们保证它是唯一的答案
示例 1:
输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-window-substring
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
题解
初始化两个指针 l 和 r 从左端向右端移动,且 l 的位置一定在 r 的左边或重合。
步骤:
- 初始化双指针( left 和 right )
- 增加右边界使滑窗包含T
- 收缩左边界直到无法再去掉元素
- 重复第 2 和第 3 步,直到 right 到达字符串 S 的尽头
- 第三步中只保留最小间距,
代码(python)
class Solution:
def minWindow(self, s: str, t: str) -> str:
need = collections.defaultdict(int)
for c in t:
need[c] += 1 #统计T字符数量
needCnt = len(t)
i = 0 #记录起始位置
res = (0, float('inf')) #用两个元素,方便之后记录起终点
#三步骤:
#1. 增加右边界使滑窗包含t
for j,c in enumerate(s):
if need[c] >0:
needCnt -= 1
need[c] -= 1 #这行放在外面不可以,看19行 need[c] == 0
#2. 收缩左边界直到无法再去掉元素 !注意,处理的是i
if needCnt == 0:
while True:
c = s[i]
if need[c] == 0: #表示再去掉就不行了(need>0)
break
else:
need[c] += 1
i += 1
if j-i < res[1] - res[0]: #这里是否减一都可以,只要每次都是这样算的就行,反正最后也是输出子串而非长度
res = (i,j)
#3. i多增加一个位置,准备开始下一次循环(注意这步是在 needCnt == 0里面进行的 )
need[s[i]] += 1
needCnt += 1 #由于 移动前i这个位置 一定是所需的字母,因此NeedCnt才需要+1
i += 1
return "" if res[1]>len(s) else s[res[0]: res[1]+1]
总结:
滑动窗口基本原理
1.窗口初始化
设置窗口初始化值为 0 ,即 left 和 right 都位于序列的左端位置。
2.窗口扩张
窗口扩张时 left 指针保持不动,向右移动 right 指针;在窗口扩张的初期,窗口右侧纳入新的元素对结果的影响时积极的,有以下两种情况:
(1).新加入的元素使窗口得到的结果更优,则每一步都是一个当前最优解;停止扩张的临界条件是新加入的元素刚好使窗口不符合条件。
(2).新加入的元素使窗口离达到目标条件更近;停止扩展的临界条件是新加入的元素刚好使窗口符合目标条件。
当窗口达到停止扩张的临界条件时,继续扩张对结果的影响时消极的,此时应该进行窗口收缩。
3.窗口收缩
窗口收缩时 right 指针保持不动,向右移动 left 指针;窗口收缩的初期,窗口左侧丢弃的元素对结果的影响的积极的,有以下两种情况:
(1).若窗口停止扩张的临界条件是新加入的元素刚好使窗口不符合目标条件,则丢弃一个旧元素可能使窗口满足条件,停止收缩的临界条件是丢弃一个元素使窗口刚好满足目标条件。
(2).若窗口停止扩张的临界条件是新加入的元素刚好使窗口符合目标条件,则丢弃一个旧元素可能使窗口的结果更优,停止收缩的临界条件是丢弃一个元素使窗口刚好不满足目标条件。
(3)当窗口达到停止收缩的临界条件时,继续收缩对结果的影响时消极的,此时应该进行窗口扩张。
4.滑动停止
滑动窗口的过程就是不断的进行窗口扩张与收缩,当窗口扩张对结果的影响使积极时就进行窗口扩张,当窗口收缩对结果的影响时积极时就进行窗口收缩,不断重复这个过程。当right 指针到达序列末尾时,窗口滑动停止。