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) ✅✅

总结

本题的关键思想是:

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

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

  • 最长无重复子串;
  • 固定长度子串统计;
  • 子数组和问题。
相关推荐
Wect14 小时前
LeetCode 130. 被围绕的区域:两种解法详解(BFS/DFS)
前端·算法·typescript
NAGNIP1 天前
一文搞懂深度学习中的通用逼近定理!
人工智能·算法·面试
颜酱1 天前
单调栈:从模板到实战
javascript·后端·算法
CoovallyAIHub2 天前
仿生学突破:SILD模型如何让无人机在电力线迷宫中发现“隐形威胁”
深度学习·算法·计算机视觉
CoovallyAIHub2 天前
从春晚机器人到零样本革命:YOLO26-Pose姿态估计实战指南
深度学习·算法·计算机视觉
CoovallyAIHub2 天前
Le-DETR:省80%预训练数据,这个实时检测Transformer刷新SOTA|Georgia Tech & 北交大
深度学习·算法·计算机视觉
CoovallyAIHub2 天前
强化学习凭什么比监督学习更聪明?RL的“聪明”并非来自算法,而是因为它学会了“挑食”
深度学习·算法·计算机视觉
CoovallyAIHub2 天前
YOLO-IOD深度解析:打破实时增量目标检测的三重知识冲突
深度学习·算法·计算机视觉
NAGNIP2 天前
轻松搞懂全连接神经网络结构!
人工智能·算法·面试
NAGNIP2 天前
一文搞懂激活函数!
算法·面试