滑动窗口题解——找到字符串中所有字母异位词【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)
优势 更快,避免多余字典比较 结构直观,便于理解
相关推荐
花开富贵ii1 小时前
代码随想录算法训练营四十三天|图论part01
java·数据结构·算法·深度优先·图论
weixin_307779131 小时前
AWS Lambda解压缩S3 ZIP文件流程
python·算法·云计算·aws
code小毛孩2 小时前
leetcode hot100数组:缺失的第一个正数
数据结构·算法·leetcode
legendary_bruce8 小时前
【22-决策树】
算法·决策树·机器学习
艾伦~耶格尔9 小时前
【数据结构进阶】
java·开发语言·数据结构·学习·面试
max50060010 小时前
基于桥梁三维模型的无人机检测路径规划系统设计与实现
前端·javascript·python·算法·无人机·easyui
闪电麦坤9511 小时前
数据结构:N个节点的二叉树有多少种(Number of Binary Trees Using N Nodes)
数据结构·二叉树·
快去睡觉~12 小时前
力扣400:第N位数字
数据结构·算法·leetcode
qqxhb13 小时前
零基础数据结构与算法——第七章:算法实践与工程应用-搜索引擎
算法·搜索引擎·tf-idf·倒排索引·pagerank·算法库
gzzeason14 小时前
LeetCode Hot100:递归穿透值传递问题
算法·leetcode·职场和发展