【面试经典 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.rhkb.cn/news/144280.html

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

相关文章

JS三大运行时全面对比:Node.js vs Bun vs Deno

全文约 5100 字&#xff0c;预计阅读需要 15 分钟。 JavaScript 运行时是指执行 JavaScript 代码的环境。目前&#xff0c;JavaScript 生态中有三大运行时&#xff1a;Node.js、Bun、Deno。老牌运行时 Node.js 的霸主地位正受到 Deno 和 Bun 的挑战&#xff0c;下面就来看看这…

计算机视觉与深度学习-循环神经网络与注意力机制-RNN(Recurrent Neural Network)、LSTM-【北邮鲁鹏】

目录 举例应用槽填充&#xff08;Slot Filling&#xff09;解决思路方案使用前馈神经网络输入1-of-N encoding(One-hot)&#xff08;独热编码&#xff09; 输出 问题 循环神经网络&#xff08;Recurrent Neural Network&#xff0c;RNN&#xff09;定义如何工作学习目标深度Elm…

Vue中自定义实现类似el-table的表格效果实现行颜色根据数据去变化展示

主要使用div布局实现表格效果&#xff0c;并使用渐变实现行背景渐变的效果 页面布局 <div class"table-wrap"><div class"table-title"><divv-for"(item, index) in tableColumn":key"index":prop"item.prop&qu…

Windows11安装MySQL8.1

安装过程中遇到任何问题均可以参考(这个博客只是单纯升级个版本和简化流程) Windows安装MySQL8教程-CSDN博客 到官网下载mysql8数据库软件 MySQL :: Download MySQL Community Server 下载完后,解压到你需要安装的文件夹 其中的配置文件内容了如下 [mysqld]# 设置3306端口po…

使用applescript自动化trilium的数学公式环境

众所周知&#xff0c;trilium什么都好&#xff0c;就是对数学公式的支持以及markdown格式的导入导出功能太拉了&#xff0c;而最拉的时刻当属把这两个功能结合起来的时候&#xff1a;导入markdown文件之后&#xff0c;原来的数学公式全没了&#xff0c;需要一个一个手动用ctrlm…

python安装第三方模块方法

正常情况下安装python第三方模块没啥说的&#xff0c;但是由于python安装模块默认是在外网下载安装&#xff0c;牵扯外网网速问题&#xff0c;所以可以配置下使用国内某镜像源来下载模块 python -m pip install xxxxxxxxxxx 和 pip install xxxxxxxxxx 的命令都可下载安装第三…

R语言用标准最小二乘OLS,广义相加模型GAM ,样条函数进行逻辑回归LOGISTIC分类...

原文链接&#xff1a;http://tecdat.cn/?p21379 本文我们对逻辑回归和样条曲线进行介绍&#xff08;点击文末“阅读原文”获取完整代码数据&#xff09;。 logistic回归基于以下假设&#xff1a;给定协变量x&#xff0c;Y具有伯努利分布&#xff0c; 目的是估计参数β。 回想一…

一篇博客学会系列(1) —— C语言中所有字符串函数以及内存函数的使用和注意事项

目录 1、求字符串长度函数 1.1、strlen 2、字符串拷贝(cpy)、拼接(cat)、比较(cmp)函数 2.1、长度不受限制的字符串函数 2.1.1、strcpy 2.1.2、strcat 2.1.3、strcmp 2.2、长度受限制的字符串函数 2.2.1、strncpy 2.2.2、strncat 2.2.3、strncmp 3、字符串查找函数…

正态分布的概率密度函数|正态分布检验|Q-Q图

正态分布的概率密度函数&#xff08;Probability Density Function&#xff0c;简称PDF&#xff09;的函数取值是指在给定的正态分布参数&#xff08;均值 μ 和标准差 σ&#xff09;下&#xff0c;对于特定的随机变量取值 x&#xff0c;计算得到的概率密度值 f(x)。这个值表示…

借助 ControlNet 生成艺术二维码 – 基于 Stable Diffusion 的 AI 绘画方案

背景介绍 在过去的数月中&#xff0c;亚马逊云科技已经推出了多篇博文&#xff0c;来介绍如何在亚马逊云科技上部署 Stable Diffusion&#xff0c;或是如何结合 Amazon SageMaker 与 Stable Diffusion 进行模型训练和推理任务。 为了帮助客户快速、安全地在亚马逊云科技上构建、…

快速将iPhone大量照片快速传输到电脑的办法!

很多使用iPhone 的朋友要将照片传到电脑时&#xff0c;第一时间都只想到用iTunes 或iCloud&#xff0c;但这2个工具真的都非常难用&#xff0c;今天小编分享牛学长苹果数据管理工具的照片传输功能&#xff0c;他可以快速的将iPhone照片传输到电脑上&#xff0c;并且支持最新的i…

【Linux】JumpServer 堡垒机远程访问

文章目录 前言1. 安装Jump server2. 本地访问jump server3. 安装 cpolar内网穿透软件4. 配置Jump server公网访问地址5. 公网远程访问Jump server6. 固定Jump server公网地址 前言 JumpServer 是广受欢迎的开源堡垒机&#xff0c;是符合 4A 规范的专业运维安全审计系统。JumpS…

Docker 容器跨主机通信 - Flannel

Author&#xff1a;rab 目录 前言一、架构及环境二、服务部署2.1 Etcd 部署2.2 Flannel 部署2.3 Docker 网络配置 三、容器通信验证及路由分析3.1 通信验证3.2 路由转发分析3.3 数据分发分析 总结 前言 今天是中秋佳节&#xff0c;首先在此祝大家“中秋快乐&#xff0c;阖家团…

RDMA技术(解决主从数据库数据不一致问题)

优质博文&#xff1a;IT-BLOG-CN 一、简介 RDMA(remote direct memory access)即远端直接内存访问&#xff0c;是一种高性能网络通信技术&#xff0c;具有高带宽、低延迟、无CPU消耗等优点。 主要解决网络传输中服务器端数据处理的延迟问题。 Remote&#xff1a;数据通过网络…

机器人过程自动化(RPA)入门 3. 顺序、流程图和控制流程

到目前为止&#xff0c;我们已经了解了RPA是什么&#xff0c;并且我们已经看到了通过记录任务的活动并运行它来训练UiPath机器人是多么简单。使用记录器的UiPath可以很容易地自动化日常任务。在我们开始自动化复杂的任务之前&#xff0c;让我们学习如何控制从一个到另一个的活动…

传统遗产与技术相遇,古彝文的数字化与保护

古彝文是中国彝族的传统文字&#xff0c;具有悠久的历史和文化价值。然而&#xff0c;由于古彝文的形状复杂且没有标准化的字符集&#xff0c;对其进行文字识别一直是一项具有挑战性的任务。本文介绍了古彝文合合信息的文字识别技术&#xff0c;旨在提高古彝文的自动识别准确性…

分类预测 | MATLAB实现WOA-FS-SVM鲸鱼算法同步优化特征选择结合支持向量机分类预测

分类预测 | MATLAB实现WOA-FS-SVM鲸鱼算法同步优化特征选择结合支持向量机分类预测 目录 分类预测 | MATLAB实现WOA-FS-SVM鲸鱼算法同步优化特征选择结合支持向量机分类预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 MATLAB实现WOA-FS-SVM鲸鱼算法同步优化特征选择结…

Pikachu靶场——XXE 漏洞

文章目录 1. XXE1.1 查看系统文件内容1.2 查看PHP源代码1.3 查看开放端口1.4 探测内网主机 1. XXE 漏洞描述 XXE&#xff08;XML External Entity&#xff09;攻击是一种利用XML解析器漏洞的攻击。在这种攻击中&#xff0c;攻击者通过在XML文件中插入恶意实体来触发解析器加载…

直播软件开发技巧:7个实时视频传输和弹幕功能的关键步骤

近年来&#xff0c;随着直播行业的快速崛起&#xff0c;直播软件的开发变得越来越重要。直播软件的成功不仅依赖于稳定的实时视频传输&#xff0c;还需要强大的弹幕功能来提升用户体验。作为直播软件开发领域的专家&#xff0c;我将与您分享七个关键步骤&#xff0c;帮助您掌握…

用友移动管理系统任意文件上传漏洞

一、漏洞描述 用友移动管理系统 uploadApk.do 文件存在任意文件上传 二、fofa查询 body"../js/jslib/jquery.blockUI.js" 三、漏洞利用 poc POST /maportal/appmanager/uploadApk.dopk_obj HTTP/1.1 Host: ip:port Cache-Control: max-age0 Upgrade-Insecure-Req…