滑动窗口题解——找到字符串中所有字母异位词【LeetCode】

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

两种方法:定长滑窗/不定长滑窗

方法一:定长滑窗

✅ 算法思路

目标:找出字符串 s 中所有 p 的字母异位词的起始索引。

核心思路是:滑动窗口 + 哈希表比较字符频次

步骤详解:
  1. 定义两个字典(collections.Counter):

    • cnt_p:统计字符串 p 中每个字符出现的次数。

    • cnt_s:用来统计 s 中滑动窗口内的字符频次。

  2. 使用滑动窗口遍历 s

    • 每次向右滑动一位,记录当前字符(c)的频率到 cnt_s 中。

    • left = right - len(p) + 1 是当前滑动窗口的左边界。

      • 如果 left < 0,说明窗口长度还不够,跳过本次比较。
    • 若当前窗口内字符频率与 p 的字符频率相同(cnt_s == cnt_p),说明这是一个异位词的起始位置,加入结果列表。

    • 移除窗口最左边的字符计数(cnt_s[s[left]] -= 1),为下次滑动窗口做准备。

python 复制代码
# 请使用 Python3 提交代码!Python2 已经被淘汰了
class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        ans = []
        cnt_p = Counter(p)  # 统计 p 的每种字母的出现次数
        cnt_s = Counter()  # 统计 s 的长为 len(p) 的子串 s' 的每种字母的出现次数
        for right, c in enumerate(s):
            cnt_s[c] += 1  # 右端点字母进入窗口
            left = right - len(p) + 1
            if left < 0:  # 窗口长度不足 len(p)
                continue
            if cnt_s == cnt_p:  # s' 和 p 的每种字母的出现次数都相同
                ans.append(left)  # s' 左端点下标加入答案
            cnt_s[s[left]] -= 1  # 左端点字母离开窗口
        return ans

在一个窗口内,如果字母出现次数相同,那么就判断是字母异位词;

⏱️ 时间复杂度分析

设:

  • n 是字符串 s 的长度;

  • m 是字符串 p 的长度;

  • a 是字符集大小(最多 26 个小写字母)。

每一步操作:
  • 每个字符只进入和离开窗口一次:共 O(n)

  • 每次比较 cnt_s == cnt_p

    • 在 Python 中,Counter 的比较会遍历内部 key。

    • 最坏情况下是 O(a),即 26 个小写字母。

综合时间复杂度:
  • 总体为:O(n * a) ,在本题中 a = 26 是常数,因此可以认为是 O(n)

🧠 空间复杂度分析

  • cnt_pcnt_s 都是 Counter 对象,最多存储 26 个字母的频次。

  • 所以:空间复杂度是 O(a) = O(26) = O(1)(常数级)

  • ans 最多包含 n - m + 1 个索引,最坏为 O(n)

总空间复杂度:O(n)(主要是结果数组,计数器是常数空间)

方法二:不定长滑窗

✅ 算法思路

💡 目标:

在字符串 s 中查找所有与字符串 p字母异位词的子串起始索引。

📌 思路核心:
  • 使用 滑动窗口 + Counter 频次计数表

  • 与前一个版本不同的是:只使用一个计数器 cnt,而非同时维护两个窗口计数器

  • 在窗口中每加入一个字符,就将 cnt[c] -= 1

  • 如果某个字符计数小于 0,说明此字符出现次数过多,则不断收缩左边界(恢复字符计数);

  • 当窗口长度刚好等于 p 长度时,表示当前窗口是合法的异位词,记录其起始位置。

python 复制代码
class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        ans = []
        cnt = Counter(p)  # 统计 p 的每种字母的出现次数
        left = 0
        for right, c in enumerate(s):
            cnt[c] -= 1  # 右端点字母进入窗口
            while cnt[c] < 0:  # 字母 c 太多了
                cnt[s[left]] += 1  # 左端点字母离开窗口
                left += 1
            if right - left + 1 == len(p):  # s' 和 p 的每种字母的出现次数都相同
                ans.append(left)  # s' 左端点下标加入答案
        return ans
🧠 举个例子:
python 复制代码
s = "cbaebabacd", p = "abc"
  • 初始化 cnt = Counter({'a':1, 'b':1, 'c':1})

  • 当遍历到前三个字符 "cba" 时,窗口长度等于 3,且所有 cnt 中字符都被消耗为 0,说明是一个异位词 → 加入答案。

  • 如果某字符被多加了(cnt[c] < 0),就从左侧不断弹出直到窗口合法。

⏱️ 时间复杂度分析

设:

  • n = len(s),字符串 s 的长度;

  • m = len(p),字符串 p 的长度;

  • a = 26,英文小写字母数量。

✅ 主体操作:
  • 遍历一次 s,每个字符至多进入窗口一次,离开窗口一次;

  • 每个字符进入/退出窗口都只涉及对 cnt 的简单加减操作,是 O(1)

  • 整体的操作是 线性的扫描和窗口移动

✅ 总时间复杂度:
  • O(n)

🧠 空间复杂度分析

  • cnt 最多维护 26 个小写字母的计数;

  • ans 最多存储 O(n) 个索引位置。

✅ 总空间复杂度:
  • O(1)cnt 是常数空间)+ O(n)(结果数组)

🔁 对比:

项目 本版本(单 Counter + 窗口平衡) 前版本(双 Counter 比较)
比较逻辑 巧妙用计数平衡维护合法窗口 每轮完整比较两个 Counter
时间复杂度 O(n)(无需逐字符比较) O(n * a)(每轮比较 26 字符)
空间复杂度 O(1) + O(n) O(1) + O(n)
优势 更快,避免多余字典比较 结构直观,便于理解
相关推荐
小庞在加油2 分钟前
《dlib库中的聚类》算法详解:从原理到实践
c++·算法·机器学习·数据挖掘·聚类
ComputerInBook5 分钟前
C++ 标准模板库算法之 transform 用法
开发语言·c++·算法·transform算法
hn小菜鸡6 小时前
LeetCode 377.组合总和IV
数据结构·算法·leetcode
Deepoch7 小时前
Deepoc 大模型:无人机行业的智能变革引擎
人工智能·科技·算法·ai·动态规划·无人机
heimeiyingwang9 天前
【深度学习加速探秘】Winograd 卷积算法:让计算效率 “飞” 起来
人工智能·深度学习·算法
时空自由民.9 天前
C++ 不同线程之间传值
开发语言·c++·算法
ai小鬼头9 天前
AIStarter开发者熊哥分享|低成本部署AI项目的实战经验
后端·算法·架构
小白菜3336669 天前
DAY 37 早停策略和模型权重的保存
人工智能·深度学习·算法
zeroporn9 天前
以玄幻小说方式打开深度学习词嵌入算法!! 使用Skip-gram来完成 Word2Vec 词嵌入(Embedding)
人工智能·深度学习·算法·自然语言处理·embedding·word2vec·skip-gram
亮亮爱刷题9 天前
飞往大厂梦之算法提升-7
数据结构·算法·leetcode·动态规划