📜  滑动窗口注意(1)

📅  最后修改于: 2023-12-03 15:11:08.895000             🧑  作者: Mango

滑动窗口

滑动窗口是一种通过维护窗口内的状态来解决一类字符串/数组问题的算法。其基本原理是维护一个窗口,通过滑动窗口的方式改变窗口的大小,再通过更新窗口内的状态来得到问题的解。

应用场景

滑动窗口通常用于以下场景:

  • 从字符串/数组中找出一个子串/子数组,该子串/子数组满足某种条件,比如字符出现频率大于等于某个值、子数组的和等于某个值等等。
  • 利用滑动窗口解决一些关于字符串/数组的最优化问题,比如最小覆盖子串,最长重复子字串等。
算法步骤

滑动窗口算法的基本步骤如下:

  1. 初始化窗口的起点和终点;
  2. 移动右端点,扩大窗口,直到满足条件为止;
  3. 移动左端点,缩小窗口,直到不满足条件为止;
  4. 对每个满足条件的窗口进行更新。

根据具体问题的不同,滑动窗口算法通常还需要一些额外的技巧,下面我们介绍一些常见的技巧。

技巧
判断窗口状态

最基础的技巧就是判断窗口的状态。对于问题中给出的某种条件,我们需要判断当前窗口是否满足该条件。通常情况下,判断窗口状态的复杂度是$O(1)$的,因为只需要更新窗口左右端点时更新状态即可。

以字符串中最小覆盖子串为例,窗口状态表示为窗口内每个字符出现的次数。对于每个新的右端点,我们需要将对应的字符计数加一,比较当前窗口是否满足条件。对于每个新的左端点,我们需要将对应的字符计数减一,以维护窗口状态。

剪枝优化

为了减少不必要的计算,我们可以使用剪枝防止窗口冗余。比如在字符串中最小覆盖子串问题中,如果一个字符在当前窗口内出现的次数小于该字符在目标串中出现的次数,那么该字符在后续的窗口中也不可能成为答案,我们可以直接将窗口右端点移动过去。

计算窗口大小

在有些问题中,需要计算窗口的大小。通常情况下,窗口大小的计算复杂度是$O(1)$的,因为只需要在左右端点变化时更新即可。

窗口移动

窗口移动即是改变窗口的大小。窗口移动可以是一个单位一个单位地移动,也可以是同时移动多个单位。在字符串中最小覆盖子串问题中,通常会同时移动左右两个端点,而在求数组中最大连续子数组和问题中,通常会一边移动右端点,一边更新答案。

代码实现

以字符串中最小覆盖子串问题为例,下面给出滑动窗口的实现。

def minCoverSubstring(s, t):
    from collections import Counter

    window = Counter()
    min_len = float('inf')
    res = ''

    left, right = 0, 0

    target = Counter(t)

    while right < len(s):
        # 更新窗口状态
        c = s[right]
        window[c] += 1
        right += 1

        # 若窗口满足条件,则移动左端点
        while all(map(lambda x: window[x] >= target[x], target)):
            # 更新答案
            if right - left < min_len:
                min_len = right - left
                res = s[left:right]

            # 移动左端点
            c = s[left]
            window[c] -= 1
            left += 1

    return res