【面试经典 150 | 滑动窗口】串联所有单词的子串

文章目录

  • 写在前面
  • Tag
  • 题目来源
  • 题目解读
  • 解题思路
    • 方法一:两个哈希表
    • 方法二:滑动窗口
  • 写在最后

写在前面

本专栏专注于分析与讲解【面试经典150】算法,两到三天更新一篇文章,欢迎催更……

专栏内容以分析题目为主,并附带一些对于本题涉及到的数据结构等内容进行回顾与总结,文章结构大致如下,部分内容会有增删:

  • Tag:介绍本题牵涉到的知识点、数据结构;
  • 题目来源:贴上题目的链接,方便大家查找题目并完成练习;
  • 题目解读:复述题目(确保自己真的理解题目意思),并强调一些题目重点信息;
  • 解题思路:介绍一些解题思路,每种解题思路包括思路讲解、实现代码以及复杂度分析;
  • 知识回忆:针对今天介绍的题目中的重点内容、数据结构进行回顾总结。

Tag

【滑动窗口】【字符串】


题目来源

面试经典 150 | 30. 串联所有单词的子串


题目解读

找出字符串数组 words 中字符串按任意顺序组成的新字符串在字符串 s 中的开始索引,以数组的形式返回。其中,words 中所有字符串的长度都相同。


解题思路

首先,我们记 sLen 为字符串 s 的长度,wsLen 为字符串数组 words 的长度,wLen 为字符串数组中每个字符串的长度。

本题的解题思路其实一目了然:判断 s 中长度为 wsLen * wLen 的子串是否可以由 words 中字符串按任意顺序组合而成(下文以匹配代之),如果可以的话,那么长度为 wsLen * wLens 子串的开始索引就是有效的答案,加入答案数组 res 即可。

本题关键就是如何判断是否 “匹配”。

方法一:两个哈希表

我们使用两个哈希表来辅助 “匹配”。

第一个哈希表 mp1 用来记录 words 中的字符串出现的次数,第二个哈希表 mp2 用来记录当前长度为 wsLen * wLen 的子串中长度为 wLen 的字符串出现次数,第二个哈希表更新结果如下图所示,其中字符串 s = barfoochewsLen = 1wLen = 3

如果 mp2 中有任何一个字符串出现的次数大于在 mp1 中出现的次数,或者mp2 中有一个字符串没有在 mp1 中出现过,则匹配失败。

具体实现中,我们可以边更新 mp2,边匹配:

  • 迭代长度为 wsLen * wLens 子串中的所有字符串进行,记当前的字符串为 ss
  • 首先判断 ss 是否在 mp1 中,如果不在,当前长度为 wsLen * wLens 子串一定不匹配;
  • 如果在,则更新 mp2,接着判断 mp2[ss]mp1[ss] 的大小关系,如果前者大,则当前长度为 wsLen * wLens 子串一定不匹配。
  • 如果迭代完长度为 wsLen * wLens 子串中的所有字符串都没有出现以上不匹配的情况,则说明长度为 wsLen * wLens 子串的开始索引就是有效的答案,加入答案数组 res 即可。

但是,实际测试,方法一超时。我觉得问题在于【先枚举滑窗再枚举单词数】,如果像答案那样【先枚举单词数,再跳跃枚举滑窗】

还有【一次哈希】的解法,不论是一次哈希还是两次哈希最坏的时间复杂度都达到 了 1 0 8 10^8 108,是这样计算的 1 0 4 × 1 0 3 × 30 = 1 0 8 10^4 \times 10^3 \times 30 = 10^8 104×103×30=108,官方题解的时间复杂度为 1 0 3 × 1 0 4 ÷ 30 × 30 = 1 0 7 10^3 \times 10^4 \div 30 \times 30 = 10^7 103×104÷30×30=107,就差那一点最后两个测试用例就没通过。

该方法超时了,实现代码 就不贴出来了。

方法二:滑动窗口

此时利用的是 438. 找到字符串中所有字母异位词 方法二中的优化版的滑动窗口来解决,可以参考 滑窗 differ 优化 进行理解。

其实方法一也是利用的滑动窗口,其中的长度为 wsLen * wLens 子串就是一个固定的窗口下的子串,不同于方法一【先枚举所有的滑窗再枚举单词数】,方法二的滑窗是【先枚举单词数,再跳跃枚举滑窗】,现在来具体看一看是如何实现的。

首先需要将 s 划分为单词组,每个单词的大小均为 wLen,一共有 wLen 中划分方式。如下图所示为 s = "barfooche"words = ["bar", "foo"] 对于 s 的三种划分方式。

(1)
(2)
(3)

划分成单词组后,一个滑窗包含 s 中前 wsLen 个单词,用一个哈希表 differ 来表示窗口内单词频次和 words 中单词频次之差。初始化 differ 时,出现在窗口中的单词,每出现一次,相应的值增加 1,出现在 words 中的单词,每出现一次,相应的值减少 1。示例代码:

// 每一种分组方式的 differ 初始化
for (int i = 0; i < n && i + m * n <= ls; ++i) {unordered_map<string, int> differ;// 每一种分组方式的窗口内 differ ++for (int j = 0; j < m; ++j) {++differ[s.substr(i + j * n, n)];}// 每一种分组方式 words 内 differ --for (string &word: words) {if (--differ[word] == 0) {differ.erase(word);}}
}

然后将窗口右移,右侧会加入一个单词,左侧会移出一个单词,并对 differ 做相应的更新。窗口移动时,若出现 differ 中值不为 0 的键的数量为 0,则表示这个窗口中的单词频次和 words 中单词频次相同,窗口的左端点是一个待求的起始位置。示例代码:

for (int start = i; start < ls - m * n + 1; start += n) {// 以初始化的 differ 为基础进行判断if (start != i) {       // start = i 时,直接判断 differ 是否为空,为空则 start = i 是一个起始位置// 先加入一个单词string word = s.substr(start + (m - 1) * n, n);if (++differ[word] == 0) {differ.erase(word);}// 后移除一个单词word = s.substr(start - n, n);if (--differ[word] == 0) {differ.erase(word);}}// 如果 differ 为空,则表示这个窗口中的单词频次和 `words` 中单词频次相同,将当前起始位置加入答案数组if (differ.empty()) {res.emplace_back(start);}
}

总的实现代码

class Solution {
public:vector<int> findSubstring(string &s, vector<string> &words) {vector<int> res;int m = words.size(), n = words[0].size(), ls = s.size();for (int i = 0; i < n && i + m * n <= ls; ++i) {unordered_map<string, int> differ;for (int j = 0; j < m; ++j) {++differ[s.substr(i + j * n, n)];}for (string &word: words) {if (--differ[word] == 0) {differ.erase(word);}}for (int start = i; start < ls - m * n + 1; start += n) {if (start != i) {string word = s.substr(start + (m - 1) * n, n);if (++differ[word] == 0) {differ.erase(word);}word = s.substr(start - n, n);if (--differ[word] == 0) {differ.erase(word);}}if (differ.empty()) {res.emplace_back(start);}}}return res;}
};

复杂度分析

时间复杂度: O ( n × m ) O(n \times m) O(n×m) n n nwords 中每个单词的长度, m m ms 的长度。

空间复杂度: O ( n × k ) O(n \times k) O(n×k) n n nwords 中每个单词的长度, k k kwords 的长度,每次滑动窗口时,需要用一个哈希表保存单词频次。


写在最后

如果文章内容有任何错误或者您对文章有任何疑问,欢迎私信博主或者在评论区指出 💬💬💬。

如果大家有更优的时间、空间复杂度方法,欢迎评论区交流。

最后,感谢您的阅读,如果感到有所收获的话可以给博主点一个 👍 哦。

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

信创办公–基于WPS的PPT最佳实践系列 (项目8创建电子相册)

信创办公–基于WPS的PPT最佳实践系列 &#xff08;项目8创建电子相册&#xff09; 目录 应用背景操作步骤 应用背景 如果我们想把图片弄成相册&#xff0c;或者弄成一段有音乐的视频分享给朋友。我们可以利用PPT来制作。那我们如何用PPT制作电子相册或视频呢&#xff1f;可以跟…

2012 款宝马 X6 xDrive35i 车 中央显示屏经常会提示“发动机异常”

故障现象 一辆2012 款宝马X6 xDrive35i车&#xff08;开发系列号为E71&#xff09;&#xff0c;搭载N55发动机&#xff0c;累计行驶里程约为21.2万km。车主反映&#xff0c;车辆加速过程中&#xff0c;中央显示屏经常会提示“发动机异常”。 故障诊断 接车后&#xff0c;进行路…

零代码编程:用ChatGPT批量将多个文件夹中的视频转为音频

有多个文件夹中的 视频&#xff0c;都要批量转换成音频格式。 转换完成后要删除视频。虽然现在已经有很多格式转换软件可以实现这个功能&#xff0c;但是需要一个个文件夹的操作&#xff0c;还要手动去删除视频。用ChatGPT来写一个批量自动操作程序吧&#xff1a; 输入提示词如…

Matlab随机数的产生

1、常见分布随机数的产生 1.1 二项分布 在贝努力试验中&#xff0c;某事件A发生的概率为p&#xff0c;重复该实验n次&#xff0c;X表示这n次实验中A发生的次数&#xff0c;则随机变量X服从的概率分布律&#xff08;概率密度&#xff09;为 记为 binopdf(x,n,p) p…

【深度学习实验】卷积神经网络(六):卷积神经网络模型(VGG)训练、评价

目录 一、实验介绍 二、实验环境 1. 配置虚拟环境 2. 库版本介绍 三、实验内容 0. 导入必要的工具包 1. 构建数据集&#xff08;CIFAR10Dataset&#xff09; a. read_csv_labels&#xff08;&#xff09; b. CIFAR10Dataset 2. 构建模型&#xff08;FeedForward&…

uni-app:获取元素宽高

效果 代码 这里我定义的宽为500px,高为200排序,控制台输出的结果是502,202。原因是我设置了上下左右宽度各为1px的border边框导致 核心代码分析 // const query uni.createSelectorQuery();表示创建了一个选择器查询实例。通过这个实例&#xff0c;你可以使用不同的方法来选择…

数据库数据恢复-SQL SERVER数据库分区被格式化的数据恢复方案

SQL SERVER数据库故障类型&#xff1a; 1、SQL SERVER数据库文件被删除。 2、SQL SERVER数据库所在分区格式化。 3、SQL SERVER数据库文件大小变为“0”。 4、使用备份还原数据库时覆盖原数据库。 SQL SERVER数据库故障原因&#xff1a; 1、人为误操作。 2、文件系统损坏&#…

vue安装步骤

1、winR ->cmd 打开运行窗口 2、如下两种方式&#xff0c;测试电脑现有vue版本&#xff0c;提示"MODULE_NOT_FOUND"错误 (1)方式一&#xff1a;vue -V (2)方式二&#xff1a;vue -version 3、输入以下命令&#xff1a; 参考链接&#xff1a;https://blog.csdn.n…

Android Logcat 命令行工具

关于作者&#xff1a;CSDN内容合伙人、技术专家&#xff0c; 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 &#xff0c;擅长java后端、移动开发、商业变现、人工智能等&#xff0c;希望大家多多支持。 目录 一、导读二、概览三、日常用法3.1 面板介绍3.2 日志过滤…

获取dom元素

<button type"button" click"greet">count is {{ count }}</button>function greet(event) {if (event) {console.log(event)console.log(event.target)console.log(event.target.tagName)} } 很明显没传参数&#xff0c;但是获取到了相应的值…

【VUE复习·3】@keyup.xxx 键盘事件触发函数(单按键 or 组合按键触发)

总览 1.keyup.xxx or keydown.xxx 单按键触发 2.组合按键触发 一、keyup.xxx or keydown.xxx 1.用法 在我们使用 keyup.enter 时&#xff0c;那么我们可以这样写&#xff1a; <div><input type"text" placeholder"按下回车键以确定..." keyu…

数据结构---课后习题(第一章)

&#x1f388;数据结构 &#x1f388;☀️☀️第一章 &#x1f388;☀️☀️&#x1f4a1;&#x1f4a1;&#x1f4a1;k阶m项斐波那契数 题目1.17&#xff1a; 已知k阶斐波那契序列的定义为 试编写求k阶斐波那契序列的第m项值的函数算法&#xff0c;k和m均以值调用的形式在函数…

二十,镜面IBL--打印BRDF积分贴图

比起以往&#xff0c;这节应该是最轻松的了&#xff0c; 运行结果如下 代码如下&#xff1a; #include <osg/TextureCubeMap> #include <osg/TexGen> #include <osg/TexEnvCombine> #include <osgUtil/ReflectionMapGenerator> #include <osgDB/Re…

新来个技术总监,把限流实现的那叫一个优雅,佩服!

新来个技术总监&#xff0c;把限流实现的那叫一个优雅&#xff0c;佩服&#xff01; 在电商高并发场景下&#xff0c;我们经常会使用一些常用方法&#xff0c;去应对流量高峰&#xff0c;比如限流、熔断、降级&#xff0c;今天我们聊聊限流。什么是限流呢&#xff1f;限流是限…

RocketMQ高性能核心原理与源码架构剖析

文章目录 1、源码环境搭建1.1、主要功能模块1.2、源码启动服务1.2.1、 启动nameServer1.2.2、 启动Broker1.2.3、 发送消息1.2.4、 消费消息 2、源码剖析2.1、NameServer的启动过程2.2、Broker服务启动过程2.3、Netty服务注册框架2.3.1、关注重点2.3.2、源码重点 1、源码环境搭…

【Linux】多线程

目录 一、线程1.线程概念2.二级页表3.线程的优点4.线程的缺点 二、进程和线程三、线程控制1.POSIX线程库2.线程创建3.线程等待4.线程终止5.线程分离 四、线程ID 一、线程 1.线程概念 什么是线程&#xff1f; 1.在一个程序里的一个执行路线就叫做线程&#xff08;thread&#x…

PS与PL与PG082

参考&#xff08;照抄自己加点&#xff09;&#xff1a; ZYNQ PS-PL数据交互方式总结&#xff08;好文&#xff09;_axi emc-CSDN博客 zynq_process是一个用于方便操作PS和PL通信的GUI。 MIO分配在bank0和bank1直接与PS部分相连&#xff0c;EMIO分配在bank2直接和PL部分…

CSS详细基础(三)复合选择器

前两章介绍了CSS中的基础属性&#xff0c;以及一些基础的选择器&#xff0c;本贴开始介绍复合选择器的内容~ ​ 在 CSS 中&#xff0c;可以根据选择器的类型把选择器分为基础选择器和复合选择器&#xff0c;复合选择器是建立在基础选择器之上&#xff0c;对基本选择器进行组合形…

时序分解 | Matlab实现CEEMD互补集合经验模态分解时间序列信号分解

时序分解 | Matlab实现CEEMD互补集合经验模态分解时间序列信号分解 目录 时序分解 | Matlab实现CEEMD互补集合经验模态分解时间序列信号分解效果一览基本介绍程序设计参考资料 效果一览 基本介绍 Matlab实现CEEMD互补集合经验模态分解时间序列信号分解 1.分解效果图 &#xff0…

Leetcode383. 赎金信

力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 给你两个字符串&#xff1a;ransomNote 和 magazine &#xff0c;判断 ransomNote 能不能由 magazine 里面的字符构成。 如果可以&#xff0c;返回 true &#xff1b;否则返回 false 。 magazine 中的每…