LeetCode LCR 015. 找到字符串中所有字母异位词 (Java)

LCR 015. 找到字符串中所有字母异位词

题目描述

给定两个字符串 sp,要求找到 s 中所有是 p 的变位词(字母相同但排列不同)的子串,并返回这些子串的起始索引。例如:

  • 输入 s = "cbaebabacd", p = "abc",输出 [0, 6],因为子串 "cba""bac""abc" 的变位词。
  • 输入 s = "abab", p = "ab",输出 [0, 1, 2],因为每个长为 2 的子串都是 "ab" 的变位词。

初始思路:暴力滑动窗口
核心思想
  1. 固定窗口长度 :窗口长度为 p.length(),逐个遍历 s 中所有可能的窗口。
  2. 统计字符频率 :对每个窗口,统计字符出现次数,与 p 的字符频率比对。
  3. 记录匹配结果 :若窗口字符频率与 p 完全一致,记录当前窗口的起始索引。
代码实现
java 复制代码
public static List<Integer> findAnagrams_2(String s, String p) {
    int windowLen = p.length();
    List<Integer> result = new ArrayList<>();
    int[] pCount = new int[26];

    // 统计 p 的字符频率
    for (char c : p.toCharArray()) {
        pCount[c - 'a']++;
    }

    for (int left = 0; left <= s.length() - windowLen; left++) {
        int[] sCount = new int[26];
        int sign = 1;

        // 统计当前窗口的字符频率
        for (int j = left; j < left + windowLen; j++) {
            sCount[s.charAt(j) - 'a']++;
        }

        // 比对频率是否一致
        for (int j = 0; j < 26; j++) {
            if (sCount[j] != pCount[j]) {
                sign = 0;
                break; // 优化点:发现不一致立即跳出
            }
        }

        if (sign == 1) {
            result.add(left);
        }
    }
    return result;
}
复杂度分析
  • 时间复杂度 :O(n * m),其中 ns 的长度,mp 的长度。每次窗口需要遍历 m 个字符,共遍历 n - m + 1 次。
  • 空间复杂度:O(1),固定使用两个长度为 26 的数组。

优化思路:动态滑动窗口
核心改进
  1. 减少重复计算:维护一个固定长度的窗口,每次移动窗口时,只需更新移除的字符和新增的字符。
  2. 差异计数器 :通过一个变量记录当前窗口与 p 的频率差异,避免每次全量比对。
优化代码
java 复制代码
public List<Integer> findAnagrams(String s, String p) {
    List<Integer> result = new ArrayList<>();
    if (s.length() < p.length()) return result;

    int[] pCount = new int[26];
    int[] sCount = new int[26];
    int windowLen = p.length();

    // 初始化 p 的字符频率和第一个窗口的 s 字符频率
    for (int i = 0; i < windowLen; i++) {
        pCount[p.charAt(i) - 'a']++;
        sCount[s.charAt(i) - 'a']++;
    }

    int diff = 0;
    for (int i = 0; i < 26; i++) {
        if (sCount[i] != pCount[i]) diff++;
    }

    if (diff == 0) result.add(0);

    // 滑动窗口:逐个右移,动态更新频率和差异
    for (int right = windowLen; right < s.length(); right++) {
        int leftChar = s.charAt(right - windowLen) - 'a';
        int rightChar = s.charAt(right) - 'a';

        // 移除左边界字符
        if (sCount[leftChar] == pCount[leftChar]) diff++;
        sCount[leftChar]--;
        if (sCount[leftChar] == pCount[leftChar]) diff--;

        // 添加右边界字符
        if (sCount[rightChar] == pCount[rightChar]) diff++;
        sCount[rightChar]++;
        if (sCount[rightChar] == pCount[rightChar]) diff--;

        if (diff == 0) {
            result.add(right - windowLen + 1);
        }
    }
    return result;
}
关键点解释
  • 窗口初始化 :初始化 p 和第一个窗口的字符频率,计算初始差异值 diff
  • 滑动更新
    • 移除左边界字符:若该字符原本频率匹配,则移除后差异增加,更新后若重新匹配则差异减少。
    • 添加右边界字符:同理更新差异值。
  • 差异判断 :当 diff == 0 时,当前窗口是变位词。
复杂度分析
  • 时间复杂度 :O(n),仅遍历 s 一次。
  • 空间复杂度:O(1),固定使用两个长度为 26 的数组。

总结

暴力滑动窗口法直观易懂,但时间复杂度较高,适用于小规模数据。优化后的动态滑动窗口通过减少重复计算,显著提升了效率,是解决此类问题的标准方法。核心在于维护窗口的字符频率差异,避免全量比对,将时间复杂度从 O(n * m) 优化到 O(n)。

相关推荐
格图素书35 分钟前
数学建模算法案例精讲500篇-【数学建模】DBSCAN聚类算法
算法·数据挖掘·聚类
DashVector2 小时前
向量检索服务 DashVector产品计费
数据库·数据仓库·人工智能·算法·向量检索
AI纪元故事会2 小时前
【计算机视觉目标检测算法对比:R-CNN、YOLO与SSD全面解析】
人工智能·算法·目标检测·计算机视觉
夏鹏今天学习了吗2 小时前
【LeetCode热题100(59/100)】分割回文串
算法·leetcode·深度优先
卡提西亚2 小时前
C++笔记-10-循环语句
c++·笔记·算法
还是码字踏实2 小时前
基础数据结构之数组的双指针技巧之对撞指针(两端向中间):三数之和(LeetCode 15 中等题)
数据结构·算法·leetcode·双指针·对撞指针
KYGALYX2 小时前
在Linux中备份msyql数据库和表的详细操作
linux·运维·数据库
余—笙3 小时前
Linux(docker)安装搭建CuteHttpFileServer/chfs文件共享服务器
linux·服务器·docker
lang201509283 小时前
Linux高效备份:tar与gzip完全指南
linux·运维·服务器
IDOlaoluo3 小时前
OceanBase all-in-one 4.2.0.0 安装教程(CentOS 7/EL7 一键部署详细步骤)
linux·centos·oceanbase