【力扣-438. 找到字符串中所有字母异位词】Python笔记

0 阅读4分钟

一、题目描述

给定两个字符串 sp,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引

  • 字母异位词:指由相同字母重排列形成的字符串(字符种类和频次完全相同,顺序无关)。
  • 输入示例:s = "cbaebabacd", p = "abc"
  • 输出示例:[0, 6]
  • 解释:s 中索引 0 的子串 "cba"、索引 6 的子串 "bac" 都是 "abc" 的字母异位词。

二、解题思路:固定长度滑动窗口 + 数组计数

核心逻辑(循序渐进版)

  1. 问题拆解:要找所有长度等于 len(p) 的子串,且子串与 p 字符频次完全一致。
  2. 暴力解法痛点:枚举所有长度为 len(p) 的子串,逐个统计频次对比,时间复杂度 O (n*m)(n=len (s), m=len (p)),效率低。
  3. 滑动窗口优化:用双指针维护固定长度的窗口,滑动时仅更新「进入窗口」和「离开窗口」的字符频次,避免重复统计,时间复杂度降至 O (n)。
  4. 数组计数选型:因仅涉及小写字母,用长度 26 的数组替代哈希表,查询 / 更新速度更快,且可直接通过 == 对比频次。

具体步骤

  • 步骤 1:初始化两个长度为 26 的数组,分别统计 p 的字符频次 p_count
  • 步骤 2:用右指针遍历 s,将字符加入窗口并更新 s_count
  • 步骤 3:当窗口长度超过 len(p) 时,左指针右移,移出左侧字符并更新 s_count
  • 步骤 4:窗口长度等于 len(p) 时,对比 s_countp_count,相同则记录左指针索引。
class Solution: 
    def findAnagrams(self, s: str, p: str) -> list[int]: 
        len_p = len(p) 
        len_s = len(s) # 边界处理:p比s长,直接返回空列表 
        if len_p > len_s: 
            return []
        res = []
        # 初始化26位字母频次数组(对应a-z)
        p_count = [0] * 26
        s_count = [0] * 26
        # 统计p的字符频次
        for char in p: 
            # 转换为0-25的索引(a->0, b->1...) 
            p_count[ord(char) - ord('a')] += 1
        # 初始化第一个窗口(前len_p个字符) 
        for i in range(len_p): 
            s_count[ord(s[i]) - ord('a')] += 1 
        # 检查第一个窗口是否匹配 
        if s_count == p_count: 
            res.append(0)
        # 滑动窗口遍历剩余字符
        for right in range(len_p, len_s): 
            # 右指针:新字符进入窗口,频次+1 
            in_char = s[right] 
            s_count[ord(in_char) - ord('a')] += 1
            # 左指针:旧字符离开窗口,频次-1 
            out_char = s[right - len_p]
            s_count[ord(out_char) - ord('a')] -= 1 
            # 检查当前窗口是否匹配,匹配则记录起始索引 
            if s_count == p_count: 
                res.append(right - len_p + 1) 
        return res

四、核心知识点深度解析

1. 固定长度滑动窗口

  • 适用场景:需要找固定长度的子串 / 子数组(如本题找长度 = len (p) 的异位词)。
  • 核心优势:双指针同向移动,无回溯,线性时间复杂度。
  • 对比可变窗口:可变窗口(如 LC3 无重复最长子串)需动态调整窗口大小,固定窗口只需维护边界滑动。

2. 数组计数 vs 哈希表计数

方式优点缺点适用场景
数组计数速度快、可直接对比仅支持有限字符集小写字母 / ASCII 字符
哈希表计数支持任意字符集对比需遍历键值对包含特殊字符 / 大小写混合

五、扩展知识(提升文章吸引力)

1. 性能优化:diff 变量减少对比开销

上述代码每次对比两个数组需 O (26) 时间,可引入diff变量记录频次不一致的字符数:

# 优化版核心逻辑(仅展示关键部分) 
diff = 0 # 初始化diff:统计频次不同的字符数
for i in range(26): 
    if p_count[i] != s_count[i]: 
        diff += 1 
 # 滑动时仅更新diff 
 if diff == 0: 
     res.append(...) # 无差异则匹配

优化后对比操作从 O (26) 降至 O (1),常数时间更优。

2. 同类题目联动

  • LC567. 字符串的排列(判断是否存在异位词子串)
  • LC76. 最小覆盖子串(可变窗口 + 哈希表)
  • LC3. 无重复字符的最长子串(可变窗口)

总结

  1. 本题核心是固定长度滑动窗口 + 26 位数组计数,将暴力 O (n*m) 优化为 O (n);
  2. 数组计数仅适用于有限字符集,对比哈希表更高效,可直接用==判断频次是否一致;
  3. 窗口滑动时需注意「进入 / 离开」字符的频次更新,以及起始索引的正确计算。