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

文章目录

一、题目描述

给定两个字符串 s 和 p,请你在 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 = si..i+len(p)-1
统计 sub 的字符频率
freqsub == freqp?
记录索引 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

代码逻辑流程

是
否
是
相同
不同
开始
初始化 need26, window26
计算 p 中的字母频率 need
left=0, right=0
遍历 s
将 sright 加入 window
right++
窗口大小 > len(p)?
移除 sleft , 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) ✅✅

总结

本题的关键思想是:

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

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

  • 最长无重复子串;
  • 固定长度子串统计;
  • 子数组和问题。
相关推荐
XuCoder11 小时前
改一个数,右边全得重算,这题怎么扛住两万次查询
算法
小羊没烦恼!11 小时前
在Scrum中实施敏捷建模
java·开发语言·windows·算法·c#
weixin_3077791311 小时前
基于睿擎工业开发平台的预训练视觉模型轻量化适配与低代码部署优化
开发语言·算法
hold?fish:palm12 小时前
45 二叉树的右视图
开发语言·c++·算法
粤鼎恒业13 小时前
惠州工厂电子料回收厂家推荐:从交接单反推筛选标准
大数据·数据库·算法·硬件架构·硬件工程·pcb工艺·材料工程
小爷毛毛(卓寿杰)13 小时前
【Agent 意图识别】输出协议、评估与置信度
人工智能·算法·大模型·prompt·大语言模型·agent
优质酸酸乳13 小时前
力扣100题之21合并两个有序链表
算法·leetcode·链表
旖旎夜光13 小时前
LeetCode 137 : 只出现一次的数字 II(位运算) —— 题解
c++·笔记·学习·算法·leetcode·力控
旋生万物13 小时前
Agent System Prompt 注入螺旋公理:让 AI 推理不跑偏的可复制模板(附完整 Prompt)
人工智能·算法
深圳雨林凯AI14 小时前
图案裂变的空间关系原理:元素放大为何必然挤压负空间,以及三个可调变量|雨林凯AI技术拆解
人工智能·算法