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

专题二_滑动窗口_串联所有单词的子串

前提:

此题难度较大,但是和上一道题有极大的相似!所以看了上一道题,会更易理解!专题二_滑动窗口_找到字符串中所有字母异位词-CSDN博客!

一:题目

解释:上一道题异位词的单位是字符,此题异位词单位是字符串罢了!

二:算法

和上一题的区别:

1:哈希表的类型<string,int>

解释:我们不能再用数组模拟哈希表了,上一道题能是因为字符可以通过减去'a'得到0~25的下标,但此题是字符串,我们无法将其转换为下标,所以直接用哈希表,哈希表中含有哈希函数,将字符串转换为下标!int则代表该字符串的个数!


2:left和right的移动步长为len个

解释:因为异位词的单位字符串,所以双指针的步长为一个字符串的长度。而题目中已经说清楚了words字符串数组中的每个字符串长度相同,这样步长len一开始就可以固定了!


3:滑动窗口不再只执行一次

Q1:为什么不再只执行一次?

A1:

输入:s = "barfoothefoobarman", words = ["foo","bar"]
输出:[0,9]

情况1:仅单词滑动窗口:

这种情况单词滑动窗口就已经足够了,但是有些情况,不止一次

情况2:需多次滑动窗口:

输入:s = "barfoothefoobarman", words = ["arf","oot"]
输出:[1]

此时,我们仍然进行从下标为0的滑动窗口:

我们再从下标为1开始:

解释:此时才检索到了正确的答案!所以要从不同起点开始滑动窗口才能够检测到所有的单词!

Q2:为什么窗口一会是3个字符,一会是6个字符?

A2:无论是2还是4,都不会影响什么,因为我们只会从right开始向后读取和words中元素等长的字符来判断时候是有效的单词,比如"arf",此时right位于a,"arfoot",此时right位于o,所以只用保证窗口内的单词的个数不会超过words的元素个数即可!

类比上道题:我们只会保证窗口内的字符个数不会超过目标字符串的长度,只不过此题的单位不同罢了,单词的长度是固定的,所以保证窗口被单词的个数,不超过words中的即可!

Q3:那应该执行几次滑动窗口?

A3:words中的单个单词有多长,则应该执行多少次,如果words中的单词长度为3,则应该从s字符串的下标0,下标1,下标2,开始执行三次滑动窗口,这样才能获取到所有可能!而从下标3开始执行互动窗口,其实就是从下标0执行滑动窗口的第二步罢了!

如下图:

解释:所以从3开始本质就是从0开始,从4开始本质就是从1开始,从5开始本质就是从2开始....。所以执行多少次,根据words中的单词的长度而定!!

①:暴力

暴力解法就是没有用滑动窗口,这意味着每次left要以不同的元素作为下标,然后right向右遍历,列举出所有的结果可能:

解释:left从0,1,2,3,4...开始作为起点,每次内部都向右划分6个字符作为一种可能去判断,其次每次有了新的可能,会把旧可能从哈希表中剔除,然后放进新的可能,所以很麻烦!

②:滑动窗口

而滑动窗口,本质就是我们发现双指针可以同向移动,当你right向右遍历想进窗口的时候,则你左面从left开始截取一个单词长度取出窗口即可,所以双指针同向移动,使用滑动窗口!

但是滑动窗口有很多注意的点!

1:哈希表的选择

我们选择的是unodered_map,首先存储的是kv值,所以选择map,其次我们不关心是否有序,所以选择unodered,其次我们会经常插入删除哈希表,而unodered_map的底层是哈希,map的底层是红黑树,哈希不换是删除还是插入的速度都远高于红黑树!所以选择unodered_map!

2:滑动窗口for循环上限

在上道题中right的上限<n(字符串的元素个数)即可,但是此题不再是了!

我们的right代表的从s字符串中,从right处开始向右面取一个单词长度,去进窗口,所以我们一定要考虑right+len和n的关系,要让不同起点的滑动窗口,最后一次也能安全得取到一个完整的单词!

所以right+len应该最多刚好等于n,也就是right+len<=n!

Q1:为什么<=n,n代表的是字符串的元素个数,==n不是会越界吗?

A1:等于n的确会越界,但是如果right=5,现在单词长度为3,则我们应该从下标5开始向右取到7,则现在下标567这三个字符作为一个单词,但是我们的写法是right+len--->5+3=8,所以我们的写法就多加了一个!所以才会写作<=,当然你也可以写成:right+len-1 < s.size()

所以写法为两种:right+len-1 < s.size();  和 right+len <= s.size();

Q2:那right < s.size()len+1;  和 right<= s.size()-len;呢?

A2:万万不可,这样写必错!

报错如下:

含义为:调用 substr() 方法时发生了越界访问,也就是使用substr()的时候,要么参数就已经越距了,要么取截取字符的时候,某些字符越距!

这是因为:right < s.size()len+1;  和 right<= s.size()-len;的写法具有潜在风险!

以 right<= s.size()-len为例:

假设:

  • s.size() = 5(字符串长度)

  • len = 10(单词长度)

  • right = 0(初始位置)

危险写法:

right <= s.size() - len  
→ 0 <= 5 - 10  
→ 0 <= 4294967291(因为 size()函数返回的是size_t,其是是无符号整数,-5会变成正数)
→ 结果为 true(错误判断!)

导致会继续调用substr(right,len),导致越界!

三:代码

①:最优版本

class Solution {
public:vector<int> findSubstring(string s, vector<string>& words) {vector<int> ret;//返回值unordered_map<string, int> hash2;//存储words的哈希表for (auto& str : words)//将words存储进hash2中hash2[str]++;int len = words[0].size();//words中单词的长度int num = words.size();//words中单词的个数for (int i = 0; i < len; i++) //滑动窗口的次数{unordered_map<string, int> hash1;//每次滑动窗口适应的哈希表for (int left = i, right = i, count = 0; right + len <= s.size(); right += len) {//进窗口string in = s.substr(right, len);//先得到进窗口的元素hash1[in]++;//存储进哈希表if (hash2.count(in) && hash1[in] <= hash2[in]) //进窗口后,窗口哈希表的v值<=存储words哈希表的v值 代表是是有效频次!count++;//窗口长度长处words数组的长度(数组元素*数组个数)while (right - left + len > len * num) {//出窗口string out = s.substr(left, len);//先得到出窗口的元素if (hash2.count(out) && hash1[out] <= hash2[out])//存储进哈希表之后后,窗口哈希表的v值<=存储words哈希表的v值 代表是是有效频次!count--;hash1[out]--;//从哈希表中移除left += len;//移动步长为len}if (count == num)//判断存储结果ret.push_back(left);}}return ret;}
};

解释:最优版本,不仅采取了unordered_map和滑动窗口,还在每次比较两个哈希表元素的v值之前,先判断了hash2(存储words元素)是否存在该元素,不存在,则直接不用比较

易错:hash1作为每次滑动窗口使用的哈希表,应该在两层for循环之间定义,这样才能保证每次滑动窗口用的哈希表是新创建的,所以是干净的,hash1放在两层for外定义,则每次滑动窗口会被一轮滑动窗口残留在哈希表中的数据影响!

②:一般版本

下面是你选择map 和 直接比较两个哈希表的代码,也是可以通过的,但不够优秀!

class Solution {
public:vector<int> findSubstring(string s, vector<string>& words) {vector<int> ret;//返回值map<string, int> hash2;//存储words的哈希表for (auto& str : words)//将words存储进hash2中hash2[str]++;int len = words[0].size();//words中单词的长度int num = words.size();//words中单词的个数for (int i = 0; i < len; i++) //滑动窗口的次数{map<string, int> hash1;//每次滑动窗口适应的哈希表for (int left = i, right = i, count = 0; right + len <= s.size(); right += len) {//进窗口string in = s.substr(right, len);//先得到进窗口的元素hash1[in]++;//存储进哈希表if (hash1[in] <= hash2[in]) //进窗口后,窗口哈希表的v值<=存储words哈希表的v值 代表是是有效频次!count++;//窗口长度长处words数组的长度(数组元素*数组个数)while (right - left + len > len * num) {//出窗口string out = s.substr(left, len);//先得到出窗口的元素if (hash1[out] <= hash2[out])//存储进哈希表之后后,窗口哈希表的v值<=存储words哈希表的v值 代表是是有效频次!count--;hash1[out]--;//从哈希表中移除left += len;//移动步长为len}if (count == num)//判断存储结果ret.push_back(left);}}return ret;}
};

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

相关文章:

  • SQL约束:数据完整性的守护者
  • 编程基础之多维数组——同行列对角线的格
  • 2.变量和常量
  • 【秋招笔试】2025.08.09美团秋招算法岗机考真题-第二题
  • 深度解析1688关键字搜索API接口:技术实现与应用探索
  • 电脑本地摄像头做成rtsp流调用测试windows系统中
  • 托福阅读记录
  • Shell脚本-四则运算符号
  • spring-boot-starter-data-redis 与 org.redisson 区别 联系
  • Shell脚本-数组定义
  • 数据结构:栈和队列(Stack Queue)基本概念与应用
  • 从0开始的中后台管理系统-5(userList页面功能实现)
  • JS数组排序算法
  • 第三章 向量
  • ECharts Y轴5等分终极解决方案 - 动态适配缩放场景
  • 计算机网络:(十四)传输层(下)详细讲解TCP报文段的首部格式,TCP 可靠传输的实现与TCP 的流量控制
  • 一些js数组去重的实现算法
  • Android的事件分发流程、Kotlin协程、4大组件、Handler机制、架构设计、性能优化、内存泄漏
  • 系统架构设计师备考之架构设计高级知识
  • Flink提交流程全解析:从模式到实践
  • DevOps:从GitLab .gitlab-ci.yml 配置文件到CI/CD
  • [论文阅读] 人工智能 + 软件工程 | 大型语言模型对决传统方法:多语言漏洞修复能力大比拼
  • FlinkSQL Joins全解析
  • 从MySQL到大数据平台:基于Spark的离线分析实战指南
  • Spark学习(Pyspark)
  • 在VMware中安装统信UOS桌面专业版
  • 可视化程序设计(4) - 第一个图形窗口程序
  • Python元组
  • 计算XGBoost分类模型的错误率
  • Qt 框架全面解析:从基础到应用