LeetCode Hot100(8/100)—— 438. 找到字符串中所有字母异位词

文章目录

一、题目描述

给定两个字符串 sp,请你在 s找出所有 p 的字母异位词的起始索引

字母异位词指包含相同字母、但排列顺序不同的字符串。

示例:

复制代码
输入: s = "cbaebabacd", p = "abc"
输出: [0,6]

解释:
起始索引为 0 的子串是 "cba",是 "abc" 的字母异位词。
起始索引为 6 的子串是 "bac",是 "abc" 的字母异位词。

二、问题分析

本题的核心是判断字母组成是否一致。

即:

给定字符串 s,判断 s 的所有长度为 p.length() 的子串中,哪些与 p 含有相同字符频次

关键在于:

  • 判断两个字符串是否为异位词 → 比较 26 个英文字母频次是否相同。
  • 遍历 s 时,为了避免重复计算,可使用"滑动窗口"。

三、解法一:暴力法

思路

  1. 遍历 s 的每一个长度为 len(p) 的子串;
  2. 统计该子串中字符频率;
  3. p 的字符频率比较;
  4. 若相同,则记录其起点索引。

流程图



开始
计算模式串 p 的字符频率
遍历 s 中每个起始位置 i
截取子串 sub = s[i..i+len(p)-1]
统计 sub 的字符频率
freq[sub] == freq[p]?
记录索引 i
继续下一轮
结束遍历
返回结果列表

时间复杂度

  • 统计每个窗口频率 O(n × 26)
  • 总体时间复杂度 O(n × m),m = |p|
  • 空间复杂度 O(26)
    暴力法在大字符串下效率较低。

四、解法二:滑动窗口法(推荐解法)

核心思路

使用 长度固定的滑动窗口 统计当前窗口中的字符频次。

只在窗口边界上进行更新操作,避免重复计算。

  1. 用两个数组统计字符出现次数:
    • need[26]:p 中每个字母出现次数
    • window[26]:当前窗口中字母出现次数
  2. 用两个指针维护一个长度固定的窗口 [left, right)
  3. 每当窗口长度等于 p.length() 时,比较两数组是否相等;若相等则记录起点索引。
  4. 向右滑动窗口时,删除左侧字符,添加右侧字符,更新频次数组。

动态示意图

假设:

复制代码
s = "cbaebabacd"
p = "abc"
步骤 窗口位置 窗口子串 是否为异位词 记录
1 [0,2] cba [0]
2 [1,3] bae /
3 [2,4] aeb /
4 [3,5] eba /
5 [4,6] bab /
6 [5,7] aba /
7 [6,8] bac [0,6]

代码逻辑流程




相同
不同
开始
初始化 need[26], window[26]
计算 p 中的字母频率 need
left=0, right=0
遍历 s
将 s[right] 加入 window
right++
窗口大小 > len(p)?
移除 s[left] , left++
继续
窗口大小 == len(p)?
比较 window 与 need
记录 left 索引
继续
继续向右移动窗口
遍历结束
返回结果


五、代码实现(Java)

java 复制代码
import java.util.*;

class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        List<Integer> res = new ArrayList<>();
        if (s.length() < p.length()) return res;

        int[] need = new int[26];
        int[] window = new int[26];
        for (char c : p.toCharArray()) {
            need[c - 'a']++;
        }

        int left = 0, right = 0;
        while (right < s.length()) {
            window[s.charAt(right) - 'a']++;
            right++;

            // 当窗口大小超过 p 的长度时,移除左侧字符
            if (right - left > p.length()) {
                window[s.charAt(left) - 'a']--;
                left++;
            }

            // 当窗口大小刚好等于 p 时,比较频率表
            if (right - left == p.length()) {
                if (Arrays.equals(need, window)) {
                    res.add(left);
                }
            }
        }
        return res;
    }
}

六、复杂度分析

项目 时间复杂度 空间复杂度 说明
暴力法 O(n × m) O(26) 每次重新统计窗口频率
滑动窗口法 O(n) O(26) 仅更新边界字符频率

七、优化思路

优化一:字符数量匹配计数器

  • 维护一个变量 matchCount 表示当前有多少字符频率相等。
  • matchCount == 26 时,表示两个字符串异位词匹配。
  • 减少了数组比较的 O(26) 操作。

优化二:提前跳过无效字符

  • 若 s 仅包含 a-z,则无;若字符集更大,可跳过 p 中未出现的字符区域。

八、总结对比

解法 思路 时间复杂度 是否推荐
暴力法 枚举所有长度 m 的子串逐一判断 O(n × m)
滑动窗口 维护频率数组,窗口移动更新 O(n) ✅ 推荐
滑动窗口 + 计数优化 增加 matchCount 降低比较代价 O(n) ✅✅

总结

本题的关键思想是:

"固定窗口大小 + 动态维护词频 + 线性滑动"

这种滑动窗口模板不仅能解决异位词匹配问题,也广泛应用于:

  • 最长无重复子串;
  • 固定长度子串统计;
  • 子数组和问题。
相关推荐
Wei&Yan33 分钟前
数据结构——顺序表(静/动态代码实现)
数据结构·c++·算法·visual studio code
团子的二进制世界1 小时前
G1垃圾收集器是如何工作的?
java·jvm·算法
吃杠碰小鸡1 小时前
高中数学-数列-导数证明
前端·数学·算法
故事不长丨1 小时前
C#线程同步:lock、Monitor、Mutex原理+用法+实战全解析
开发语言·算法·c#
long3161 小时前
Aho-Corasick 模式搜索算法
java·数据结构·spring boot·后端·算法·排序算法
近津薪荼1 小时前
dfs专题4——二叉树的深搜(验证二叉搜索树)
c++·学习·算法·深度优先
熊文豪1 小时前
探索CANN ops-nn:高性能哈希算子技术解读
算法·哈希算法·cann
熊猫_豆豆2 小时前
YOLOP车道检测
人工智能·python·算法
艾莉丝努力练剑2 小时前
【Linux:文件】Ext系列文件系统(初阶)
大数据·linux·运维·服务器·c++·人工智能·算法
偷吃的耗子2 小时前
【CNN算法理解】:CNN平移不变性详解:数学原理与实例
人工智能·算法·cnn