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

news2024/11/27 3:37:11

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 "";
        else
            return 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.coloradmin.cn/o/1530696.html

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

相关文章

【Godot4.2】 基于SurfaceTool的3D网格生成与体素网格探索

概述 说明&#xff1a;本文基础内容写于2023年6月&#xff0c;由三五篇文章汇总而成&#xff0c;因为当时写的比较潦草&#xff0c;过去时间也比较久了&#xff0c;我自己都得重新阅读和理解一番&#xff0c;才能知道自己说了什么&#xff0c;才有可能重新优化整理。 因为我对…

使用WordPress在US Domain Center上建立摄影网站的详细教程

第一部分&#xff1a;介绍摄影网站 摄影网站是摄影师展示作品、分享经验、提供服务的在线平台。在摄影网站上&#xff0c;摄影师可以展示自己的摄影作品、发布摄影日志、接受客户预约等。使用WordPress搭建摄影网站具有灵活性和可扩展性&#xff0c;可以通过选择适合的主题和插…

视频转文字怎么转?这几个转换方法收藏一下

视频转文字怎么转&#xff1f;在信息爆炸的时代&#xff0c;视频内容日益丰富&#xff0c;但如何快速、准确地提取视频中的关键信息却成为了一个挑战。本文将为你详细介绍视频转文字的方法&#xff0c;让你轻松提取视频内容&#xff0c;提高信息获取效率。 方法一&#xff1a;清…

【赠书第21期】游戏力:竞技游戏设计实战教程

文章目录 前言 1 竞技游戏设计的核心要素 1.1 游戏机制 1.2 角色与技能 1.3 地图与环境 2 竞技游戏设计的策略与方法 2.1 以玩家为中心 2.2 不断迭代与优化 2.3 营造竞技氛围与社区文化 3 实战案例分析 4 结语 5 推荐图书 6 粉丝福利 前言 在数字化时代的浪潮中&…

IO多分复用

#include<myhead.h> #define SER_PORT 8888 //服务器端口号 #define SER_IP "192.168.65.131" //服务器IPint main(int argc, const char *argv[]) {//1、创建一个套接字int sfd -1;sfd socket(AF_INET, SOCK_STREAM, 0); //参数1&#xff1a;…

win10共享了连接打印机报错提示0000011B

在Windows 10系统中&#xff0c;当用户尝试连接共享打印机时遇到错误代码0x0000011B&#xff0c;通常表示存在与打印机驱动程序或系统更新相关的兼容性问题。以下是针对这个问题的一些解决方案&#xff1a; 卸载特定系统更新&#xff1a; 根据多个来源的信息&#xff0c;错误0x…

软考考哪个好?软考中级到高级方向该如何计划

刚接触软考的朋友可能对软考的科目选择比较迷茫&#xff0c;不知道自己该怎么选方向&#xff0c;怎么选级别&#xff0c;这属于再正常不过的事情了&#xff0c;毕竟谁看到这些都可能有些迷茫&#xff0c;要理清自己的选择还是要从自身的几个方面开始的。 软考的考试专业类别就…

vue2从基础到高级学习笔记

在实际的工作中,我常使用vue的用法去实现效果,但是你要是问我为什么这样写,它的原理是啥就答不上来了。对vue的认知一直停留在表面,写这篇文章主要是为了理清并弄透彻vue的原理。 学习目标 1 学会一些基本用法的原理 2 弄懂vue核心设计原理 3 掌握vue高级api的用法 一 vue…

【前端寻宝之路】学习和总结HTML的标签属性

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法|MySQL| ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不…

【项目管理后台】Vue3+Ts+Sass实战框架搭建一

项目管理后台 建立项目最好是卸载Vetur 新建.env.d.ts文件安装Eslint安装校验忽略文件添加运行脚本 安装prettier新建.prettierrc.json添加规则新建.prettierignore忽略文件 安装配置stylelint新建.stylelintrc.cjs 添加后的运行脚本配置husky配置commitlint配置husky 强制使用…

resize-observer源码解读

resize-observer github 地址&#xff1a;https://github.com/devrelm/resize-observer 本地启动 npm installnpm startnode 18.16.0 (npm 9.5.1) 启动失败报错 node:internal/crypto/hash:71this[kHandle] new _Hash(algorithm, xofLen);^Error: error:0308010C:digital …

spark RDD 创建及相关算子

RDD编程入口 RDD编程入口对象是SparkContext对象&#xff0c;想要调用相关的计算api都需要通过构造出的sparkcontext对象调用 RDD的创建 通过并行化集合创建RDD&#xff08;本地集合转为分布式&#xff09;&#xff0c;api如下 rdd sc.parrallize(param1, param2)参数1是本…

第112讲:Mycat实践指南:字符串Hash算法分片下的水平分表详解

文章目录 1.字符串Hash算法分片的概念1.1.字符串Hash算法的概念1.2.字符串Hash算法是如何将数据路由到分片节点的 2.使用字符串Hash算法分片对某张表进行水平拆分2.1.在所有的分片节点中创建表结构2.2.配置Mycat实现字符串Hash算法分片的水平分表2.2.1.配置Schema配置文件2.2.2…

边缘检测-Tiny and Efficient Model for the Edge Detection Generalization

源代码: https://github.com/xavysp/TEED 论文地址&#xff1a;https://arxiv.org/pdf/2308.06468.pdf 大多数高级计算机视觉任务依赖于低级图像操作作为其初始过程。边缘检测、图像增强和超分辨率等操作为更高级的图像分析提供了基础。在这项工作中&#xff0c;我们考虑三个…

公众号获取token失败,当日access_token超过1万次处理

问题&#xff1a;如果你当日的 access_token 获取次数已经超过了 1 万次&#xff0c;那么很有可能是由于频繁获取 access_token 而被微信限制了。在这种情况下&#xff0c;你需要等待到下一个自然日或者等待一段时间后再尝试获取 access_token。或者直接去公众号去刷新掉用量。…

JAVA EE (计算机是如何工作的)

学前注意事项 出去面试的时候java岗位不需要懂前端&#xff08;会少量讲解&#xff09; 但是我们做项目的时候多少回用到一些前端的东西 1.什么是计算机 1.1前情提要 不仅仅只有电脑是计算机 计算机还不仅仅是电脑手机和平板 路由器 智能洗衣机 刷脸打卡机都可以说是计算…

MySQL中数据库表的监控

MySQL中数据库表的监控 &#xff08;1&#xff09;查看数据库中当前打开了哪些表&#xff1a;show OPEN TABLES &#xff0c;如图6-1-5所示。另外&#xff0c;还可以通过show OPEN TABLES where In_use > 0过滤出当前已经被锁定的表。 查看数据库中表的状态&#xff1a;SHO…

月销12万,卖出6万件,1688跨境热销榜第一是它!

店雷达1688热销商品榜&#xff0c;食品类目下&#xff0c;月度排名第一的热销商品是这款【三只松鼠_多味鹌鹑蛋混合口味 休闲零食小吃卤蛋夜宵熟食】月销售额达到12万&#xff0c;一个月内卖出5万笔&#xff0c;复购率保持在51%。看来我们的这款中国小吃&#xff0c;也收到很多…

linux -- I2C设备驱动 -- MS32006(低压5V多通道电机驱动器)

产品简述 MS32006 是一款多通道电机驱动芯片, 其中包含两路步进电机驱动, 一路直流电机驱动; 每个通道的电流最高电流1.0A; 支持两相四线与四相五线步进电机。芯片采用 I2C 的通信接口控制模式, 兼容 3.3V/5V 的标准工业接口。 MS32006 总共集成了两路步进电机驱动器与一…

数据分析能力模型分析与展示

具体内容&#xff1a; 专业素质 专业素质-01 数据处理 能力定义•能通过各种数据处理工具及数据处理方法&#xff0c;对内外部海量数据进行清洗和运用&#xff0c;提供统一数据标准&#xff0c;为业务分析做好数据支持工作。 L1•掌握一…