力扣(串联所有单词的子串)

串联所有单词的子串问题:多滑动窗口与哈希表的实战应用。

一、题目分析

在这里插入图片描述

(一)问题定义

给定字符串 s 和字符串数组 wordswords 中所有单词长度相同 ),找出 s 中所有“串联子串”的起始索引。串联子串指包含 words 中所有单词(任意顺序连接)的子串。例如 words = ["ab","cd","ef"] 时,"abcdef""abefcd" 等符合要求,而顺序混乱或缺失单词的子串则不符合。

(二)核心挑战

  1. 精确匹配:需确保子串包含 words 中所有单词,且数量一致,顺序不限。
  2. 高效遍历s 可能较长,words 单词数量和长度各异,需避免暴力枚举所有可能子串(时间复杂度过高 )。
  3. 时间复杂度控制:题目要求高效算法,需利用哈希表和滑动窗口思想,将时间复杂度优化到可接受范围。

二、算法思想:多滑动窗口 + 哈希表协同

(一)哈希表的作用

  • 词频统计:用哈希表 wordMap 统计 words 中各单词的出现次数,作为匹配依据。
  • 窗口词频对比:在滑动窗口过程中,维护当前窗口内的单词词频哈希表(windows 数组中的每个 Map ),与 wordMap 对比,判断窗口是否为串联子串。

(二)多滑动窗口的设计

由于 words 中单词长度固定(设为 wordLen ),子串需由连续的单词拼接而成。因此,以 wordLen 为间隔,设计 wordLen 个滑动窗口(对应不同起始偏移 )。例如 wordLen = 3 时,窗口起始偏移为 0、1、2 ,分别处理不同起始位置的子串,确保覆盖所有可能的串联子串。

(三)滑动窗口的移动策略

  1. 初始化窗口:对每个起始偏移 i0 <= i < wordLen ),初始化一个窗口,截取 s 中对应位置的所有单词(按 wordLen 分割 ),统计词频存入 windows[i]
  2. 动态移动窗口:窗口初始化后,以 wordLen 为步长向右滑动。每次滑动时,移除窗口左侧的旧单词,加入右侧的新单词,更新 windows[winIndex] 的词频,再与 wordMap 对比,判断是否为串联子串。

三、代码实现与详细注释

class Solution {public List<Integer> findSubstring(String s, String[] words) {int wordCount = words.length;int wordLen = words[0].length();int sLen = s.length();List<Integer> res = new ArrayList<>();//如果words的长度大于s的长度,则不可能有子串if (sLen < wordCount * wordLen || s == null || sLen == 0 || words == null || wordCount == 0) {return res;}//将word置入wordMap,用于比对和计数Map<String, Integer> wordMap = new HashMap<>();for (String word : words) {//如果word存在就在原有的数量上+1不存在为0+1;wordMap.put(word, 1 + wordMap.getOrDefault(word, 0));}//采用多滑动窗口的方式,一个下标表示一个滑动窗口Map<String, Integer>[] windows;windows = new HashMap[wordLen];//初始化多滑动窗口 i为windows中的每一个窗口下标//wordCount*wordLen是滑动窗口的大小//i+wordCount*wordLen确保当前起始位置 i 之后,存在足够长度的子串for (int i = 0; i < wordLen && i + wordCount * wordLen <= sLen; i++) {// 在外层循环中初始化每个窗口的Mapwindows[i] = new HashMap<>();//提取对应滑动窗口内的所有单词/*j=i,例:i=0即该滑动窗口是0偏移量的单词i=1即该滑动窗口是1偏移量的单词*///退出条件:j要保持在对应滑动窗口的大小中//j+=wordLen: 每次递增一个单词的长度 for (int j = i; j < i + wordCount * wordLen; j += wordLen) {String subStr = s.substring(j, j + wordLen); // j是当前单词的起始索引//对字符串进行截取,截取为一个单词一个单词windows[i].put(subStr, 1 + windows[i].getOrDefault(subStr, 0));}//判断每一个滑动窗口有没有窗口已经是子串if (windows[i].equals(wordMap)) {res.add(i);}}//移动窗口//i代表窗口的左边界//在上面已经对窗口进行初始化,起始位置从第一个窗口的下一个单词长度开始//i+wordCount*wordLen<=sLen:确保当前位置i之后有足够的长度容纳整个窗口for (int i = wordLen; i + wordCount * wordLen <= sLen; i++) {//滑动窗口的相对位置int winIndex = i % wordLen;// s.substring(i,wordLen+j)// 截取左侧单词(起始位置:i - wordLen,长度:wordLen)String pervWord = s.substring(i - wordLen, (i - wordLen) + wordLen);// 截取右侧新单词(起始位置:nextWordStart,长度:wordLen)int nextWordStart = i + (wordCount - 1) * wordLen;String nextWord = s.substring(nextWordStart, nextWordStart + wordLen);//删除左侧单词:如果在哈希表中值>1则这个word出现了1次以上,要在原值的基础上-1,而不是直接删除if(windows[winIndex].get(pervWord)>1){windows[winIndex].put(pervWord,windows[winIndex].get(pervWord)-1);}else{windows[winIndex].remove(pervWord); }//加入右侧单词windows[winIndex].put(nextWord, 1 + windows[winIndex].getOrDefault(nextWord, 0));//判断每一个滑动窗口有没有窗口已经是子串if (windows[winIndex].equals(wordMap)) {res.add(i);}}return res;}
}

(一)代码流程拆解

  1. 初始化与边界处理
    • 计算 wordCount(单词数量 )、wordLen(单词长度 )、sLens 长度 )。
    • s 长度不足容纳 words 所有单词拼接(sLen < wordCount * wordLen ),直接返回空结果。
  2. 构建 wordMap:统计 words 中各单词的词频,用于后续匹配。
  3. 多滑动窗口初始化
    • 针对每个起始偏移 i0 ~ wordLen-1 ),初始化窗口的词频表 windows[i]
    • 截取 s 中对应窗口的所有单词,统计词频。
    • 若窗口词频与 wordMap 匹配,记录起始索引 i
  4. 滑动窗口处理
    • i = wordLen 开始,继续滑动窗口。
    • 计算当前窗口的偏移索引 winIndexi % wordLen )。
    • 移除窗口左侧旧单词,更新词频;加入右侧新单词,更新词频。
    • 对比当前窗口词频与 wordMap ,匹配则记录起始索引 i

(二)关键逻辑解析

  • 多窗口设计:因单词长度固定,不同起始偏移(0 ~ wordLen-1 )的子串需独立处理。例如 wordLen=3 时,起始偏移 0、1、2 对应子串起始为 0、1、2 ,需分别用窗口覆盖。
  • 窗口词频更新:滑动时,通过“移除左侧旧单词、加入右侧新单词”的方式,避免重复截取子串(优化时间复杂度 )。利用哈希表的增删操作,高效维护窗口内的词频。
  • 匹配判断:每次窗口更新后,直接对比 windows[winIndex]wordMap ,利用哈希表的 equals 方法快速判断是否匹配。

三、复杂度分析

(一)时间复杂度

  • 初始化多窗口:共 wordLen 个窗口,每个窗口截取 wordCount 个单词(每个单词截取时间为 O(wordLen) ),总时间复杂度为 O(wordLen * wordCount * wordLen) = O(wordLen² * wordCount)
  • 滑动窗口处理:共 sLen - wordCount * wordLen 次滑动(近似 O(sLen) ),每次滑动涉及哈希表的增删查操作(均为 O(1) ,单词数量固定 ),总时间复杂度为 O(sLen)
  • 整体时间复杂度:O(wordLen² * wordCount + sLen) 。因 wordLenwordCount 通常远小于 sLen ,可近似认为是 O(sLen) ,满足题目高效要求。

(二)空间复杂度

  • 哈希表存储wordMap 存储 wordCount 个单词的词频,windows 数组存储 wordLen 个窗口的词频(每个窗口最多 wordCount 个单词 ),空间复杂度为 O(wordCount + wordLen * wordCount) = O(wordLen * wordCount)
  • 额外空间:结果列表 res 存储符合条件的起始索引,最多 O(sLen / wordLen) 个元素。整体空间复杂度为 O(wordLen * wordCount + sLen / wordLen) ,可接受。

串联所有单词的子串问题,通过多滑动窗口 + 哈希表的协同策略,巧妙解决了精确匹配与高效遍历的难题。多窗口覆盖不同起始偏移,确保不遗漏可能的子串;哈希表实现词频快速统计与对比,优化匹配效率。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/93010.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/93010.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RH134 管理基本存储知识点

1. 对 Linux 磁盘进行分区时有哪两种方案&#xff1f;分别加以详细说明。答&#xff1a;MBR分区&#xff1a;主引导记录(MBR)分区方案是运行BIOS固件的系统上的标准方案。此方案支持最 多四个主分区。在Linux系统上&#xff0c;您可以使用扩展分区和逻辑分区来创建最多…

【JS 异步】告别回调地狱:Async/Await 和 Promise 的优雅实践与错误处理

【JS 异步】告别回调地狱&#xff1a;Async/Await 和 Promise 的优雅实践与错误处理 所属专栏&#xff1a; 《前端小技巧集合&#xff1a;让你的代码更优雅高效 上一篇&#xff1a; 【JS 数组】数组操作的“瑞士军刀”&#xff1a;精通 Array.reduce() 的骚操作 作者&#xff…

23.Linux : ftp服务及配置详解

Linux &#xff1a; ftp服务及配置详解 FTP 基本概念 定义&#xff1a;文件传输协议&#xff08;File Transfer Protocol&#xff09;&#xff0c;采用 C/S 模式工作。端口&#xff1a; 控制端口&#xff1a;21数据端口&#xff1a;20FTP 工作原理模式工作流程连接发起方主动模…

悲观锁乐观锁与事务注解在项目实战中的应用场景及详细解析

在今天做的项目练习部分中真的学到了很多东西&#xff0c;也补充了许多之前遗漏或是忘记的知识点&#xff0c;但时间精力有限&#xff0c;我就先记录一下今天用到的一个新东西&#xff0c;悲观锁和乐观锁。首先给出实际应用背景&#xff1a;在加入锁和事务注解之前&#xff0c;…

Java构造器与工厂模式(静态工程方法)详解

1. 构造器1.1 构造器的核心意义1.1.1 对象初始化构造器在创建对象 (new) 时自动调用, 用于初始化对象的状态 (如设置字段初始值, 分配资源等)无构造器时: 字段为默认值&#xff08;0/null/false&#xff09;有构造器&#xff1a;确保对象创建后即处于有效状态1.1.2 强制初始化…

解决jdk初始化运行,防火墙通信选错专业网络问题

问题描述新项目添加不同版本的jdk&#xff0c;运行时提示防火墙通信策略&#xff0c;选成专用网络。其他人访问后端接口时&#xff0c;提示连接失败。 解决方案&#xff1a;1、在搜索栏中输入 防火墙关键字&#xff0c;选择到防火墙和网络保护2、选择允许应用通过防火墙3、先点…

【Linux】常用命令(三)

【Linux】常用命令&#xff08;三&#xff09;1. export1.1 原理1.2 常用语法1.3 示例1.4 书中对命令的解释1.5 生效范围2. 测试服务地址与其端口能否访问2.1 nc(Netcat)命令2.2 telnet2.3 nmap2.4 curl命令 (适用于HTTP/HTTPS 服务)1. export export 是 Linux Shell&#xff…

Pytest项目_day15(yaml)

YAMLYAML是一个对所有编程语言都很友好的数据序列化标准&#xff0c;它是一种直观的能够被电脑识别的数据序列化格式&#xff0c;是一种可读性高且容易被人类阅读的脚本语言YAML语言的本质是一种通用的数据串行化格式适用场景 可以直接序列化为数组、字典解析成本低专门写配置文…

审批流程系统设计与实现:状态驱动、灵活扩展的企业级解决方案

审批流程系统设计与实现&#xff1a;状态驱动、灵活扩展的企业级解决方案 本文基于实际企业级审批系统源码&#xff0c;深入解析如何设计高扩展性、强一致性的审批流程引擎&#xff0c;涵盖状态机设计、多租户隔离、文件服务集成等核心实现。 1. 系统设计概览 审批系统的核心架…

汽车免拆诊断案例 | 2010款奥迪A4L车行驶中发动机偶尔自动熄火

故障现象 一辆2010款奥迪A4L车&#xff0c;搭载CDZ发动机 &#xff0c;累计行驶里程约为18.2万km。该车行驶中发动机偶尔自动熄火&#xff0c;有时熄火后能够立即重新起动着机&#xff0c;有时需要等待一会儿才能重新起动着机&#xff0c;故障频率较低。因该故障在其他维修厂陆…

Liam ERD:自动生成美观的交互式实体关系图

Liam ERD 是一个可以快速生成美观且具有交互性的数据库实体关系图&#xff08;ERD&#xff09;的工具&#xff0c;可以帮助用户实现复杂数据库结构的可视化。 Liam ERD 是一个免费开源的项目&#xff0c;代码托管在 GitHub&#xff1a; https://github.com/liam-hq/liam 功能…

网络协议序列化工具Protobuf

目录前言一、下载注意二、解压安装三、Protobuf的使用1、创建.proto文件2、利用protoc编译.proto文件前言 Protocol Buffers是Google的⼀种语⾔⽆关、平台⽆关、可扩展的序列化结构数据的⽅法&#xff0c;它可⽤于&#xff08;数据&#xff09;通信协议、数据存储等。 Protoco…

从表单校验到API网关:全链路输入安全防护指南

从表单校验到 API 网关:全链路输入安全防护指南 在软件系统的安全防御体系中,输入安全是第一道防线,而这道防线的坚固程度直接决定了系统抵御外部攻击的能力。从用户在浏览器中填写表单的那一刻起,到数据经过 API 网关流转至后端服务,每一个环节都可能成为输入攻击的突破…

Flask vs Django:微框架与一站式对决

Flask 简介 1、简介 Flask诞生于2010年&#xff0c;是Armin ronacher用Python语言基于Werkzeug工具箱编写的轻量级Web开发框架&#xff0c;又称之为微框架。 "微"的含义&#xff1a;Flask旨在保持核心简洁&#xff0c;本身相当于内核&#xff0c;其他功能需通过扩展…

真实业务场景:mysql慢查询优化(从17秒的查询优化到700毫秒)

慢查询业务场景:原先在我们系统中要统计一些人员的单位 部门信息的数据情况&#xff0c;比如总的男女人数&#xff0c;每个单位下的男女人数等等&#xff0c;然后原来的sql是这样写的 根据一个单位的id 然后对一张表做出多个子查询进行查询&#xff0c;这时候统计记录 由于加载…

远程影音访问:通过 cpolar 内网穿透服务使用 LibreTV

文章目录前言【视频教程】1.关于LibreTV2.docker部署LibreTV3.简单使用LibreTV4.安装cpolar内网穿透5.配置ward公网地址6.配置固定公网地址总结LibreTV 与 cpolar 的协同应用&#xff0c;为用户打造了一条通往高清观影自由的便捷之路。通过这一方案&#xff0c;用户不仅摆脱了商…

Apache ECharts 6 核心技术解密 – Vue3企业级可视化实战指南

简介 ECharts 是百度开源的一个使用 JavaScript 实现的开源可视化库&#xff0c;它能够生动、可交互地展示数据。在 Vue3 项目中集成 ECharts 可以让你的项目更加直观和动态地呈现数据信息。 核心优势 特性SVG渲染器Canvas渲染器缩放保真度★★★★★★★☆☆☆动态交互性能…

考公VS考研,拼哪个性价比高?

即将到来下半年&#xff0c;将迎来考公和考研是两个非常重要的考试&#xff0c;也是许多年轻人为之奋斗的目标。无论是获得一份稳定的“铁饭碗”&#xff0c;还是提升学历学位获得更高的竞争力&#xff0c;都是值得努力的方向。那么&#xff0c;考公vs考研&#xff0c;到底哪个…

python2操作neo4j

环境依赖 jdk、neo4j图数据库 操作一条数据完整demo import os,json,sys,io from py2neo import Graph,Nodetry:sys.stdout io.TextIOWrapper(sys.stdout.buffer, encodingutf-8)sys.stderr io.TextIOWrapper(sys.stderr.buffer, encodingutf-8) except Exception:passcla…

AI 编程实践:用 Trae 快速开发 HTML 贪吃蛇游戏

1. 背景与目标 贪吃蛇是最适合入门的 2D 网页小游戏之一&#xff1a;规则简单、反馈清晰、可扩展空间大&#xff08;穿墙模式、道具、多食物、排行榜……&#xff09;。 demo地址&#xff1a;https://game.haiyong.site/snake-game.html 本项目的目标是&#xff1a; 纯前端、…