【十二】【算法分析与设计】滑动窗口(3)

30. 串联所有单词的子串

给定一个字符串 s 和一个字符串数组 words words 中所有字符串 长度相同

s 中的 串联子串 是指一个包含 words 中所有字符串以任意顺序排列连接起来的子串。

  • 例如,如果 words = ["ab","cd","ef"], 那么 "abcdef""abefcd""cdabef""cdefab""efabcd", 和 "efcdab" 都是串联子串。 "acdbef" 不是串联子串,因为他不是任何 words 排列的连接。

返回所有串联子串在 s 中的开始索引。你可以以 任意顺序 返回答案。

示例 1:

 

输入:s = "barfoothefoobarman", words = ["foo","bar"] 输出:[0,9]解释:因为 words.length == 2 同时 words[i].length == 3,连接的子字符串的长度必须为 6。 子串 "barfoo" 开始位置是 0。它是 words 中以 ["bar","foo"] 顺序排列的连接。 子串 "foobar" 开始位置是 9。它是 words 中以 ["foo","bar"] 顺序排列的连接。 输出顺序无关紧要。返回 [9,0] 也是可以的。

示例 2:

 

输入:s = "wordgoodgoodgoodbestword", words = ["word","good","best","word"] 输出:[]解释:因为 words.length == 4 并且 words[i].length == 4,所以串联子串的长度必须为 16。 s 中没有子串长度为 16 并且等于 words 的任何顺序排列的连接。 所以我们返回一个空数组。

示例 3:

输入:s = "barfoofoobarthefoobarman", words = ["bar","foo","the"] 输出:[6,9,12] 解释:因为 words.length == 3 并且 words[i].length == 3,所以串联子串的长度必须为 9。 子串 "foobarthe" 开始位置是 6。它是 words 中以 ["foo","bar","the"] 顺序排列的连接。 子串 "barthefoo" 开始位置是 9。它是 words 中以 ["bar","the","foo"] 顺序排列的连接。 子串 "thefoobar" 开始位置是 12。它是 words 中以 ["the","foo","bar"] 顺序排列的连接。

提示:

  • 1 <= s.length <= 10(4)

  • 1 <= words.length <= 5000

  • 1 <= words[i].length <= 30

  • words[i]s 由小写英文字母组成

我们需要再s找一个子数组,这个子数组包含words的所有的元素。返回这个子数组的首索引。

注意到words里面的单词长度都是一样的。所以我们满足要求的所有子数组,都是按照单词的长度划分的。

我们将字符串看做一个整体,问题转化为在s子数组中找words的异位词。

利用map容器模拟hash表。hash1记录words中不同字符串对应的个数,hash2记录区间[left,right]中不同字符串对应的个数。

在s中划分字符串,有len=words[0].size()种划分情况。

adfea3c18eeb40e78b5301569e9dfc15.png

对于每一种划分情况,我们只需要寻找符合要求的子数组即可。每一次遍历长度为len的字符串。

[left,right]区间,hash2记录区间[left,right]中不同字符串对应的个数,count记录区间内有效的字符个数,[left,right]长度不超过len*m。

因为[left,right]长度等于len*m的时候,就是对于left组合的一个解,对于后面的组合都不需要再考虑了,因为首索引都没有改变。

不断维护这个意义,我们每一次只需要判断count是否等于m,就知道[left,right] 是否符合条件。

当我们添加进right元素后,维护hash2和count。如果right-left+1>len*m,表示对于left的所有组合都考虑完毕了。left++。维护hash2和count。

此时[left,right]长度符合条件,hash2和count都维护成功,这就是一个可能的解,判断count==m,如果相等就是一个解。

 
class Solution {
public:vector<int> findSubstring(string s, vector<string>& words) {vector<int> ret;unordered_map<string, int> hash1;for (auto& s : words)hash1[s]++;int len = words[0].size(), m = words.size();for (int i = 0; i < len; i++) {unordered_map<string, int> hash2;for (int left = i, right = i, count = 0; right + len <= s.size();right += len) {string in = s.substr(right, len);hash2[in]++;if (hash1.count(in) && hash2[in] <= hash1[in])count++;if (right - left + 1 > m * len) {string out = s.substr(left, len);if (hash1.count(out) && hash2[out] <= hash1[out])count--;hash2[out]--;left += len;}if (count == m)ret.push_back(left);}}return ret;}
};

unordered_map<string, int> hash1; 定义了一个哈希表 hash1,用于存储 words 中每个单词的出现次数。

for (auto& s : words) hash1[s]++; 遍历单词列表 words,更新哈希表 hash1,计算每个单词的出现次数。

int len = words[0].size(), m = words.size(); 获取单词的长度 len(假设所有单词长度相同)和单词列表 words 的大小 m。

for (int i = 0; i < len; i++) { 由于单词有固定长度,所以使用多个滑动窗口,每个窗口的起始位置从 0 到 len-1。

unordered_map<string, int> hash2; 对于每个滑动窗口,定义一个哈希表 hash2,用于存储当前考察的窗口中每个单词的出现次数。

for (int left = i, right = i, count = 0; right + len <= s.size(); right += len) { 使用一个滑动窗口,窗口的左右边界分别由 left 和 right 表示,count 用来计数当前窗口内满足条件的单词数量,也就是有效字符的个数。窗口右边界 right 从 i 开始,每次移动一个单词的长度。

string in = s.substr(right, len); 获取当前右边界指向的单词 in。

hash2[in]++; 更新哈希表 hash2,计算当前窗口中每个单词的出现次数。

if (hash1.count(in) && hash2[in] <= hash1[in]) count++; 如果 in 存在于 hash1 中,并且 hash2[in] 的值不超过 hash1[in] 的值,则增加 count。

if (right - left + 1 > m * len) { 如果当前窗口的大小超过了所有单词串联后的长度,需要缩小窗口。

string out = s.substr(left, len); 获取当前左边界指向的单词 out。

if (hash1.count(out) && hash2[out] <= hash1[out]) count--; 如果 out 存在于 hash1 中,并且 hash2[out] 的值不超过 hash1[out] 的值,则减少 count。

hash2[out]--; left += len; 减少 out 在哈希表 hash2 中的计数,并将左边界向右移动一个单词的长度。

if (count == m) ret.push_back(left); 如果当前窗口内包含 words 中所有单词,则将当前左边界的索引添加到结果向量 ret 中。

时间复杂度和空间复杂度分析 时间复杂度:O(n * m * len),其中 n 是字符串 s 的长度,m 是单词列表 words 的大小,len 是单词的长度。每个窗口最多移动 n/len 次,每次移动需要 O(m * len) 的时间来处理窗口内的单词。

空间复杂度:O(m),主要是哈希表 hash1 和 hash2 的空间开销,它们存储了 words 中每个单词的出现次数。

76. 最小覆盖子串

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 ""

注意:

  • 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。

  • 如果 s 中存在这样的子串,我们保证它是唯一的答案。

示例 1:

输入:s = "ADOBECODEBANC", t = "ABC" 输出:"BANC" 解释:最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'。

示例 2:

输入:s = "a", t = "a" 输出:"a" 解释:整个字符串 s 是最小覆盖子串。

示例 3:

输入: s = "a", t = "aa" 输出: "" 解释: t 中两个字符 'a' 均应包含在 s 的子串中, 因此没有符合条件的子字符串,返回空字符串。

提示:

  • (m == s.length)

  • (n == t.length)

  • 1 <= m, n <= 10(5)

  • st 由英文字母组成

进阶:你能设计一个在 o(m+n) 时间内解决此问题的算法吗?

滑动窗口(同向双指针)优化暴力枚举:

我们需要在[left,right]中判断是否包含t字符串,可以用hash1记录t字符串中不同字符的个数,用hash2记录[left,right]记录字符串中不同字符的个数,再用一个count记录[left,right]中有效的字符个数,我们仅需要判断count是否等于t.size(),就可以直接判断[left,right]中是否包含t字符串。

count记录有效字符的具体做法,我们考虑把right字符添加进区间中,添加right之前count记录是的[left,right-1]的有效字符个数,添加进right后,维护hash2,hash2[in]++。hash2维护之后,如果hash2[in]<=hash2[in],说明新加入进来的字符是有效字符,表示right字符有意义。

我们不断判断所有的子串区间,也就是所有的left和right的组合。找到符合条件的最小区间的长度。

[left,right]当添加进right后,count==m,此时对于left,与right+1,right+2...后面的组合都可以不用考虑了,因为子串的长度一定大于left与right的组合。也就是说对于left的所有组合我们都考虑完毕了。此时left++。

left++后,对于[left,right-1]这个区间的所有组合都不符合条件,所以跳过这个组合,直接从right开始匹配。

然后count==m,对于这个left的所有组合也考虑完毕。以此类推,直到count!=m为止。

left++的时候,需要注意维护hash2和count的意义。

 
class Solution {
public:string minWindow(string s, string t) {int n = s.size(), m = t.size();if (n < m)return "";int hash1[128] = {0}, hash2[128] = {0};for (auto ch : t)hash1[ch]++;int begin = -1, minlen = INT_MAX;for (int left = 0, right = 0, count = 0; right < n; right++) {char in = s[right];hash2[in]++;if (hash2[in] <= hash1[in])count++;while (count == m) {if (right - left + 1 < minlen) {minlen = right - left + 1;begin = left;}char out = s[left];if (hash2[out] <= hash1[out])count--;hash2[out]--;left++;}}if (begin == -1)return "";elsereturn s.substr(begin, minlen);}
};

int n = s.size(), m = t.size(); 获取字符串 s 和 t 的长度。

if (n < m)return ""; 如果字符串 s 的长度小于字符串 t 的长度,返回空字符串,因为无法找到满足条件的窗口。

int hash1[128] = {0}, hash2[128] = {0}; 定义两个哈希表 hash1 和 hash2,用于存储字符串 t 中每个字符的出现次数和当前考察的窗口中每个字符的出现次数。

for (auto ch : t) hash1[ch]++; 遍历字符串 t,更新哈希表 hash1,计算 t 中每个字符的出现次数。

int begin = -1, minlen = INT_MAX; 初始化变量 begin 为-1,表示最小窗口的起始索引,初始化 minlen 为INT_MAX,表示最小窗口的长度。

for (int left = 0, right = 0, count = 0; right < n; right++) { 使用一个滑动窗口,窗口的左右边界分别由 left 和 right 表示,count 用来计数当前窗口内满足条件的字符数量,也就是有效字符数。窗口右边界 right 从0开始,遍历整个字符串 s。

char in = s[right]; 获取当前右边界指向的字符 in。

hash2[in]++; 更新哈希表 hash2,计算当前窗口中每个字符的出现次数。

if (hash2[in] <= hash1[in]) count++; 如果当前字符 in 在窗口中的出现次数不超过它在 t 中的出现次数,则增加 count。

while (count == m) { 如果当前窗口内包含所有 t 中的字符,尝试缩小窗口以找到更小的窗口。

if (right - left + 1 < minlen) { 如果当前窗口大小小于已知的最小窗口大小,更新最小窗口大小和起始索引。

minlen = right - left + 1; begin = left; 更新最小窗口的长度和起始索引。

char out = s[left]; 获取当前左边界指向的字符 out。

if (hash2[out] <= hash1[out]) count--; 如果 out 在窗口中的出现次数不超过它在 t 中的出现次数,减少 count。

hash2[out]--; left++; 减少字符 out 在哈希表 hash2 中的计数,并将左边界向右移动。 if (begin == -1)return "";elsereturn s.substr(begin, minlen);} 如果没有找到合适的窗口,返回空字符串。否则,返回从 begin 开始长度为 minlen 的子字符串。

时间复杂度和空间复杂度分析

时间复杂度:O(n),其中 n 是字符串 s 的长度。尽管有循环嵌套,但每个字符在 s 中只被访问两次(一次是当它进入窗口,一次是当它离开窗口),所以时间复杂度是线性的。

空间复杂度:O(1),因为 hash1 和 hash2 的大小固定为128(ASCII 字符集的大小),不依赖于输入数据的大小,因此可以认为是常数空间。

结尾

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。

 

同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。

 

谢谢您的支持,期待与您在下一篇文章中再次相遇!

 

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

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

相关文章

【计算机视觉】Gaussian Splatting源码解读补充(二)

第一部分 本文是对学习笔记之——3D Gaussian Splatting源码解读的补充&#xff0c;并订正了一些错误。 目录 三、相机相关scene/cameras.py&#xff1a;class Camera 四、前向传播&#xff08;渲染&#xff09;&#xff1a;submodules/diff-gaussian-rasterization/cuda_rast…

schweizer-electronic 公司 safedat2 操作使用说明

schweizer-electronic 公司 safedat2 操作使用说明

基于Java中的SSM框架实现矿场仓储管理系统项目【项目源码+论文说明】

基于Java中的SSM框架实现矿场仓储管理系统演示 摘要 随着数字化的建设&#xff0c;根据当时的发展和用户的需求&#xff0c;选择使用矿产资源管理的信息都是可以用作示范。物质生活已经达到了人们的基本要求&#xff0c;人们追求生活层次越来越高&#xff0c;享受生活成为人们…

Element Plus 基本使用

一、概述 1.1介绍 Element Plus 基本使用 element-ui 是基于vue 开发的一套ui组件库&#xff0c;提供丰富的网页开发组件&#xff0c;可用快速开发网站&#xff0c;降低前端开发成本版本 element目前有两个版本 element-ui&#xff1a;基于vue2element-plus: 基于vue3 官网地址…

机器学习:智能时代的核心引擎

目录 一、什么是机器学习 二、监督学习 三、无监督学习 四、半监督学习 五、强化学习 一、什么是机器学习 机器学习是人工智能的一个分支&#xff0c;它主要基于计算机科学&#xff0c;旨在使计算机系统能够自动地从经验和数据中进行学习并改进&#xff0c;而无需进行明确…

【数据结构】猛猛干7道链表OJ

前言知识点 链表的调试技巧 int main() {struct ListNode* n1(struct ListNode*)malloc(sizeof(struct ListNode));assert(n1);struct ListNode* n2(struct ListNode*)malloc(sizeof(struct ListNode));assert(n2);struct ListNode* n3(struct ListNode*)malloc(sizeof(struc…

从零开始学习在VUE3中使用canvas(六):lineCap(线条端点样式)

一、简介 lineCap能够让我们设置线条的端点样式&#xff0c;例如 1. butt const ctx canvas.getContext("2d");ctx.lineCap "butt"; // 默认样式&#xff0c;也可以显式指定 2.round const ctx canvas.getContext("2d");//圆头ctx.lineCap …

阿里云服务器“镜像”操作系统选择方法(超详细)

阿里云服务器镜像怎么选择&#xff1f;云服务器操作系统镜像分为Linux和Windows两大类&#xff0c;Linux可以选择Alibaba Cloud Linux&#xff0c;Windows可以选择Windows Server 2022数据中心版64位中文版&#xff0c;阿里云服务器网aliyunfuwuqi.com来详细说下阿里云服务器操…

MATLAB:函数与数值积分

一、数学函数图像的绘制 clc,clear fh (x)2*exp(-x).*sin(x); Xrange [0,8]; gx (x)3*exp(-x)*0.8.*sin(x); fplot(fh,Xrange,r-*,LineWidth,1.5) hold on grid on fplot(gx,Xrange,b-o,LineWidth,1.5) axis([-0.5,8.5,-0.1,0.85]) legend(fh (x)2*exp(-x).*sin(x),gx (x…

Docker之docker compose!!!!

一、概述 是 Docker 官方提供的一款开源工具&#xff0c;主要用于简化在单个主机上定义和运行多容器 Docker 应用的过程。它的核心作用是容器编排&#xff0c;使得开发者能够在一个统一的环境中以声明式的方式管理多容器应用的服务及其依赖关系。 也就是说Docker Compose是一个…

基于ssm的农家乐管理系统+数据库+论文+免费远程调试

开发环境 项目编号:JavaMySQL ssm231农家乐管理系统-民宿-餐饮-房间预定-vue 开发语言&#xff1a;Java 开发工具:IDEA /Eclipse 数据库:MYSQL5.7 应用服务:Tomcat7/Tomcat8 使用框架:ssmvue 项目介绍: ssm的农家乐管理系统。Javaee项目。采用M&#xff08;model&#xff09;V…

jenkins构建完成后部署到本机,无法读取容器外文件夹

项目背景&#xff1a; Dockerjenkins 构建完成后&#xff0c;要把打包的dist文件夹内容移动到网站目录 /www/wwwroot/xxxxxx 文件夹下&#xff1b;但是获取不到jenkins容器外的文件夹。 解决办法&#xff1a; 在容器中&#xff0c;添加挂载/映射本机目录&#xff0c;把网站…

【保姆级教程】YOLOv8目标检测:训练自己的数据集

一、YOLOV8环境准备 1.1 下载安装最新的YOLOv8代码 仓库地址&#xff1a; https://github.com/ultralytics/ultralytics1.2 配置环境 pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple二、数据准备 2.1 安装labelme标注软件 pip install label…

React系列 之 React进阶 含源码解读 (一)事件合成、state原理

资料来源&#xff1a;掘金课程 https://juejin.cn/book/6945998773818490884?enter_fromcourse_center&utm_sourcecourse_center 记录一些笔记 事件合成 React的事件其实是React重新实现的一套事件系统。目标是统一管理事件&#xff0c;提供一种跨浏览器一致性的事件处…

5G智能网关助力工业铸造设备监测升级

随着物联网技术的迅猛发展和工业4.0浪潮的推进&#xff0c;传统工业正面临着严峻的转型升级压力。在这一背景下&#xff0c;铸造行业——这一典型的传统重工业领域&#xff0c;也必须积极探索借助5G、物联网、边缘计算等技术提升生产经营效率的新路径。 本文就基于佰马合作伙伴…

【技巧】ChatGPT Prompt 提示语大全

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhang.cn] 主要来自&#xff1a;https://github.com/f/awesome-chatgpt-prompts ChatGPT SEO提示 Contributed by: StoryChief AI Reference: 7 Powerful ChatGPT Prompts to Create SEO Content Faster 供稿人&#xff1a;…

链表oj测试题(上)

链表的申明&#xff1a; struct ListNode {int val;struct ListNode* next; }; 1.题1 删除指定元素 例如&#xff1a;链表1 2 6 3 4 5 6&#xff0c;然后选择删除元素6&#xff0c;返回的链表为1 2 3 4 5 。 代码演示&#xff1a; typedef struct ListNode ListNode;List…

Spark与flink计算引擎工作原理

Spark是大批量分布式计算引擎框架&#xff0c;scale语言开发的&#xff0c;核心技术是弹性分布式数据集&#xff08;RDD&#xff09;可以快速在内存中对数据集进行多次迭代&#xff0c;支持复杂的数据挖掘算法及图形计算算法&#xff0c;spark与Hadoop区别主要是spark多个作业之…

什么是行业垂直类媒体?有哪些?怎么邀约

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体胡老师。 行业垂直类媒体是聚焦于特定行业或领域的媒体平台。 行业垂直类媒体不同于主流媒体&#xff0c;它们专注于提供与某个特定领域相关的深入内容和服务&#xff0c;例如商业新闻、旅游、数字…

能拍英语的搜题软件?九个免费好用的大学生搜题工具 #经验分享#知识分享#其他

积极参加社团活动和实践项目&#xff0c;可以帮助大学生拓宽人脉圈和锻炼实际操作能力。 1.粉鹿搜题 这是一个公众号 搜题拥有非常强大的题库&#xff0c;包含IT认证、建筑工程:、会计资格、教师资格、研究生、公务员等类型的题目。 下方附上一些测试的试题及答案 1、BPR基…