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

总结

本题的关键思想是:

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

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

  • 最长无重复子串;
  • 固定长度子串统计;
  • 子数组和问题。
相关推荐
郝学胜-神的一滴1 小时前
深入理解Linux套接字(Socket)编程:从原理到实践
linux·服务器·开发语言·网络·c++·程序人生·算法
DuHz2 小时前
UWB 雷达综述精读:应用、标准、信号处理、数据集、芯片与未来方向——论文阅读
论文阅读·学习·算法·信息与通信·信号处理
diediedei2 小时前
C++中的适配器模式变体
开发语言·c++·算法
Timmylyx05182 小时前
Codeforces Round 1075 (Div. 2) 题解
算法·codeforces·比赛日记
June bug2 小时前
(#数组/链表操作)最长上升子序列的长度
数据结构·程序人生·leetcode·链表·面试·职场和发展·跳槽
hadage2332 小时前
--- 力扣oj柱状图中最大的矩形 单调栈 ---
算法·leetcode·职场和发展
json{shen:"jing"}2 小时前
18. 四数之和
数据结构·算法·leetcode
千逐-沐风2 小时前
SMU-ACM2026冬训周报1st
算法
天赐学c语言2 小时前
1.25 - 零钱兑换 && 理解右值以及move的作用
c++·算法·leecode