滑动窗口题解——找到字符串中所有字母异位词【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)
优势 更快,避免多余字典比较 结构直观,便于理解
相关推荐
爱装代码的小瓶子36 分钟前
数据结构之队列(C语言)
c语言·开发语言·数据结构
爱喝矿泉水的猛男1 小时前
非定长滑动窗口(持续更新)
算法·leetcode·职场和发展
YuTaoShao1 小时前
【LeetCode 热题 100】131. 分割回文串——回溯
java·算法·leetcode·深度优先
YouQian7722 小时前
Traffic Lights set的使用
算法
go54631584653 小时前
基于深度学习的食管癌右喉返神经旁淋巴结预测系统研究
图像处理·人工智能·深度学习·神经网络·算法
aramae4 小时前
大话数据结构之<队列>
c语言·开发语言·数据结构·算法
大锦终4 小时前
【算法】前缀和经典例题
算法·leetcode
想变成树袋熊4 小时前
【自用】NLP算法面经(6)
人工智能·算法·自然语言处理
cccc来财5 小时前
Java实现大根堆与小根堆详解
数据结构·算法·leetcode
Coovally AI模型快速验证5 小时前
数据集分享 | 智慧农业实战数据集精选
人工智能·算法·目标检测·机器学习·计算机视觉·目标跟踪·无人机