滑动窗口:找到字符串中所有字母异位词

76 阅读3分钟

题目描述

438. 找到字符串中所有字母异位词

给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。

示例 1:

输入: s = "cbaebabacd", p = "abc"
输出: [0,6]
解释:
起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。
起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。

示例 2:

输入: s = "abab", p = "ab"
输出: [0,1,2]
解释:
起始索引等于 0 的子串是 "ab", 它是 "ab" 的异位词。
起始索引等于 1 的子串是 "ba", 它是 "ab" 的异位词。
起始索引等于 2 的子串是 "ab", 它是 "ab" 的异位词。

提示:

  • 1 <= s.length, p.length <= 3 * 104
  • s 和 p 仅包含小写字母

滑动窗口

使用数据结构 dict 构造一个滑动窗口,名为 window。window 中存储窗口中字符出现的次数。

若窗口内字符为 abb,window 结构如下:

window = {
    'a': 1,
    'b': 2
}
  • 将 p 中字符初始化为一个待匹配窗口 need,同样是 dict 结构,记录 p 中每个字符出现的频率。
  • 初始化一个变量 valid,记录 window 与 need 中有多少个字符出现频率相同
from collections import defaultdict

need = defaultdict(int)
for i in p:
    need[i] += i
    
  • 从s 的等一个字符开始,逐渐加入窗口,直到窗口长度为待匹配字符串 p 的长度。
  • 并判断新进入窗口的字符是否在待匹配字符中,即是否在need中。
    • 如果在 need 中,判断 window 中此字符出现频率是否等于对应在 need 中出现的频率,如果相等,将valid + 1
    • 如果 valid == len(need), 即表示滑动窗口中字符与字符出现频率与陪匹配窗口 need 一致,将 left 加入结果列表
left, right = 0, 0  # 窗口左右指针,初始状态窗口长度为 0,窗口内没值
res = []  # 结果列表
while left < right:
    right += 1  # 增加窗口长度,使窗口增加一个值
    c = s[left]  # 窗口内增加的值

    # 判断
    if c in need:
        window += 1
        if window[c] == need[c]:
            valid += 1   
  • 当滑动窗口长度等于待匹配窗口p的长度时,收缩窗口

    • 收缩窗口时,判断窗口内字符是否满足待匹配子串
    • 收缩窗口时,窗口内字符计数对应减少
    • 若左侧弹出窗口的字符,在 window 中出现的频率与在 need 中出现的频率相同,valid - 1
  • 滑动窗口长度小于待匹配窗口时,右指针右移增大窗口

if right - left == len(p):
    if valid == len(need):
        res.append(left)
    
    d = s[left]
    if d in window:
        if window[d] == need[d]:
            valid -= 1
        window[d] -= 1
    left += 1
    

完整代码

class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:

        from collections import defaultdict

        window, need = defaultdict(int), defaultdict(int)

        # 初始化 need, 子串中相同字符 +1
        for i in p:
            need[i] += 1

        left, right = 0, 0   # 左闭右开窗口
        valid = 0  # 记录窗口中字符数满足对应子串字符数的个数

        res = []
        # 创建窗口
        while right < len(s):
            c = s[right]
            right += 1
            if c in need:
                window[c] += 1

                # 窗口内此字符数量是否达到要求
                if window[c] == need[c]:
                    valid += 1

            # 当窗口长度大于子串长度时,收缩窗口, 同时判断窗口内元素是否满足子串要求
            while (right - left) == len(p):
                if valid == len(need):
                    res.append(left)

                # 窗口内数据更新
                d = s[left]
                if d in window:
                    if window[d] == need[d]:
                        valid -= 1
                    window[d] -= 1

                left += 1

        return res