当前位置: 首页 > news >正文

【LeetCode 热题 100】438. 找到字符串中所有字母异位词——(解法三)不定长滑动窗口+数组

Problem: 438. 找到字符串中所有字母异位词
题目:给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

【LeetCode 热题 100】438. 找到字符串中所有字母异位词——(解法一)定长滑动窗口+哈希表
【LeetCode 热题 100】438. 找到字符串中所有字母异位词——(解法二)定长滑动窗口+数组

整体思路

这段代码同样用于在字符串 s 中查找所有模式串 p 的异位词。它采用了一种更为巧妙和高效的 可变大小滑动窗口 算法。与前两个版本(一个用 HashMap,一个用固定大小窗口的数组)相比,此版本的核心思想是维护一个“合法”的窗口,该窗口内的字符都是 p 所需要的。

算法的整体思路可以分解为以下步骤:

  1. 初始化“需求”计数器

    • 算法使用一个大小为 26 的整型数组 cnt。这个数组的含义非常关键:它首先被初始化为模式串 p 的字符频率,代表着我们“需要”的字符及其数量。
    • 例如,如果 p = "aab",则 cnt['a'-'a'] 为 2,cnt['b'-'a'] 为 1。
  2. 滑动窗口的扩张与收缩

    • 算法使用 leftright 两个指针来定义滑动窗口 [left, right]right 指针持续向右移动,以扩大窗口。
    • 扩张与“消耗”:当 right 指针扫过 s 中的一个字符 c 时,会执行 cnt[c]--。这可以理解为“消耗”掉了一个所需的字符 c
      • 如果消耗后 cnt[c]>= 0,说明这个字符是 p 所需要的,且目前窗口内该字符的数量尚未超出 p 的需求。
      • 如果消耗后 cnt[c] < 0,这说明窗口内已经包含了多余的字符 c(即超出了 p 的需求量)。
    • 收缩以维持“合法性”
      • 一旦 cnt[c] 变为负数,就触发 while 循环。这个循环的目的是收缩窗口的左边界 left,直到窗口再次变得“合法”。
      • 收缩时,将 left 指针指向的字符“归还”给计数器(cnt[s.charAt(left) - 'a']++),然后将 left 右移。
      • 这个过程会一直持续,直到我们刚刚加入的那个多余字符 c 的计数 cnt[c] 不再为负。
  3. 判定与记录结果

    • 在每一次 right 指针移动后(并可能伴随着 left 的移动),算法会检查当前窗口 [left, right] 的大小。
    • 如果窗口大小 right - left + 1 正好等于模式串 p 的长度 m,这意味着:
      1. 窗口内没有多余的字符(因为 while 循环保证了所有 cnt 值都 >= 0)。
      2. 窗口的总字符数正好是 m
    • 这两个条件同时满足的唯一情况就是:窗口内的字符频率与 p 完全匹配。因此,这是一个异位词,将其起始索引 left 加入结果列表。

这种方法的精髓在于,它不强制窗口大小始终为 m,而是灵活地收缩窗口以排出“无效”字符,一旦窗口在“有效”状态下长度恰好为 m,就找到了一个解。

完整代码

import java.util.ArrayList;
import java.util.List;class Solution {/*** 在字符串 s 中查找 p 的所有异位词的起始索引。* 此版本使用可变大小的滑动窗口和单个计数数组,非常高效。* @param s 主字符串 (假定只包含小写字母)* @param p 模式字符串 (假定只包含小写字母)* @return 一个包含所有匹配起始索引的列表*/public List<Integer> findAnagrams(String s, String p) {// ans 用于存储结果,即所有异位词的起始索引List<Integer> ans = new ArrayList<>();int n = s.length();int m = p.length();// 如果主串比模式串还短,直接返回空列表if (n < m) {return ans;}// cnt 数组作为字符频率计数器。// 初始时,它存储了模式串 p 中需要的各个字符的数量。int[] cnt = new int[26];for (char ch : p.toCharArray()) {cnt[ch - 'a']++;}// left 是滑动窗口的左边界int left = 0;// right 是滑动窗口的右边界,它将遍历整个主串 sfor (int right = 0; right < n; right++) {// 获取右边界字符并将其映射到索引int c = s.charAt(right) - 'a';// 将该字符的所需数量减 1,表示窗口“消耗”了该字符。cnt[c]--;// 关键逻辑:处理窗口内字符冗余的情况。// 如果 cnt[c] < 0,说明窗口内字符 c 的数量已经超出了 p 的需求。// 此时需要从左侧收缩窗口,直到 cnt[c] 恢复到 0。while (cnt[c] < 0) {// "归还" 左边界字符:将其在 cnt 数组中的计数加 1。cnt[s.charAt(left) - 'a']++;// 移动左边界,缩小窗口。left++;}// 检查窗口大小是否等于模式串的长度。// 因为 while 循环保证了窗口内没有多余字符(所有 cnt[i] >= 0),// 如果此时窗口大小恰好为 m,那么它必然是一个异位词。if (right - left + 1 == m) {ans.add(left);}}// 返回最终的结果列表return ans;}
}

时空复杂度

时间复杂度:O(N + M)
  1. 模式串频率统计for (char ch : p.toCharArray()) 循环遍历模式串 p 一次,时间复杂度为 O(M),其中 Mp 的长度。
  2. 滑动窗口遍历
    • 外层的 for 循环由 right 指针驱动,从 0n-1,所以 right 指针总共移动了 N 次。
    • 内层的 while 循环由 left 指针驱动。虽然它在 for 循环内部,但 left 指针也只是一直向右移动,永不后退。
    • 在整个算法的生命周期中,left 指针最多从 0 移动到 n
    • 每个字符 s.charAt(i) 最多被 right 指针访问一次,也最多被 left 指针访问一次。因此,两个指针的总移动次数是线性的,约为 2N
    • 所有数组操作 cnt[...]--cnt[...]++ 都是 O(1) 的。

综合分析
总的时间复杂度是预处理 p 的时间加上两个指针遍历 s 的时间,即 O(M) + O(N) = O(N + M)。这是一个非常高效的线性时间复杂度。

空间复杂度:O(k) 或 O(1)
  1. 主要存储开销:算法只使用了一个额外的整型数组 cnt
    • 该数组的大小是 26,这是一个固定的常数,代表了字符集的大小(k=26)。它不随输入 sp 的长度而改变。
  2. 结果列表ans 列表用于存储输出,其空间不计入算法的辅助空间复杂度。
  3. 其他变量n, m, left, right, c 等都占用常数空间 O(1)。

综合分析
算法所需的额外辅助空间主要由 cnt 数组决定。由于其大小是固定的,空间复杂度为 O(k),其中 k=26。因为 k 是一个常数,所以通常也称其空间复杂度为 O(1)

http://www.lryc.cn/news/576477.html

相关文章:

  • 构建 AI 系统的 4 大 Agentic AI 设计模式
  • 网关ARP防护的措施
  • qt和qtcreator版本关系
  • n8n-nodes-puppeteer截图中文变方块乱码解决方法
  • 在单片机中如何实现一个shell控制台
  • Launcher3中的CellLayout 和ShortcutAndWidgetContainer 的联系和各自职责
  • 前端react面试题之实现网页多选搜索框
  • 【学习笔记】深入理解Java虚拟机学习笔记——第12章 Java内存模型与线程
  • python中学物理实验模拟:瞬间推力与摩擦力作用下的物体运动
  • 力扣网C语言编程题:在数组中查找目标值位置之二分查找法
  • 解决cursor无法下载插件等网络问题
  • 深入详解:随机森林算法——概念、原理、实现与应用场景
  • screen用法
  • Gradio全解13——MCP详解(4)——TypeScript包命令:npm与npx
  • 服务器的维护技术都有哪些?
  • Flutter基础(Isolate)
  • 【论文阅读笔记】知网SCI——基于主成分分析的空间外差干涉数据校正研究
  • 开疆智能CCLinkIE转ModbusTCP网关连接傲博机器人配置案例
  • 舵机在不同类型机器人中的应用
  • JVM调优实战 Day 10:性能指标采集与可视化
  • 【闲谈】技术债:软件开发的隐形杀手
  • Redis高级数据结构深度解析:BitMap、布隆过滤器、HyperLogLog与Geo应用实践
  • JWT认证性能优化实战指南
  • 《剖开WebAssembly 2.0:C++/Rust内存管理困局与破局》
  • 剑指offer48_两个链表的第一个公共节点
  • 叉车考试真题(含答案)pdf下载
  • 告别脚本!用浏览器为 AWS CLI 实现真正的 Cognito 单点登录
  • 案例开发 - 日程管理系统 - 第一期
  • PostgreSQL对比Mysql
  • WPS之PPT镂空效果实现