面试经典150题——最小覆盖子串(困难)

news2024/11/26 16:27:15

"The greatest glory in living lies not in never falling, but in rising every time we fall." - Nelson Mandela​

an aerial view of a body of water surrounded by land

1. 题目描述

2.  题目分析与解析

2.1 思路一——暴力求解

还是和之前讲的一样,看见题目没思路,先试试普通情况下人的解法,也就是暴力求解。如果让一个人来解决该题目,那就是尝试从S字符串的每个位置作为起始位置,然后向后遍历,看是否能那囊括所有 t 中的字符,能囊括就记录位置与长度,不能囊括就记录为空,最后返回最短的子串。

代码思路:

  1. 初始化,对于 t 中字符,使用一个hashMap存储,键为字母,值为出现的次数

  2. 外层for循环遍历 s 中所有字符,作为起始字符

  3. 内层for循环遍历以每个起始字符出发向后尝试囊括 t 中字符

    1. 走到 s 尾部仍然不行,那就返回空串 (因为第一个字符作为起始位置都不能涵盖 t 所有字符,那也就是遍历了整个 s 都无法涵盖t,肯定无解)

    2. 能够囊括便停止,记录起始位置和终止位置

  4. 返回最短的解

但是很显然,这种算法时间复杂度很高,通过嵌套循环遍历字符串s,试图找到包含t中所有字符的最小窗口。对于s中的每一个字符(外层循环),它会迭代剩余的字符(内层循环)来找到这样的窗口。这导致了最坏情况下的时间复杂度为O(n^2),其中n是字符串s的长度。这是因为对于s中的每个字符,我们在最坏的情况下可能需要扫描它右侧的每个其他字符。同时还需要遍历 t 字符串,因此导致时间复杂度为O(n^2 \cdot m)。

现在我们考虑如何优化代码。

2.2 思路二——滑动窗口

这个题目起始和上一篇文章《串联所有单词的子串》题目非常的相似,如果大家对于滑动窗口没有解决该题目没有什么头绪可以先回头看看上个题目。

因为和上一篇内容十分相似,我就直接给出解决办法。

  • 我们需要指定一个左右指针,开始时左右指针都指向 s 的首字符,然后右指针不断向右移动,直到囊括了 t (也就是找到了第一个解)。

  • 这时我们移动左指针,将左指针不断加一,并判断在这种情况下是否还能囊括 t ,如果出现了不能囊括的情况,我们继续移动右指针,直到再次囊括 t 。

  • 然后继续移动左指针不断加一,并判断在这种情况下是否还能囊括 t ,以此循环,直到右指针走到结尾。

这样做之所以是正确的是因为每一次左指针的移动实际上就是以左指针指向的字符作为开始位置的判断(类似于暴力求解将,以左指针作为起始字符,向后寻找解)。

而之所以这样做很高效就是因为我在每一个位置都能利用窗口中的信息,从而减少了对窗口部分的判断,大大缩减了时间开销。

代码思路:

  1. 初始化哈希表

    1. 窗口计数表(windowCounts):同时,创建另一个哈希表来追踪当前滑动窗口中每个字符出现的次数。

    2. 频率表(freqMap):首先创建一个哈希表来存储字符串t中每个字符出现的次数。这是因为我们需要知道窗口中必须包含哪些字符,以及这些字符各需要出现多少次,才能认为窗口满足条件。

  2. 使用两个指针表示窗口的边界

    1. 左指针(left)和右指针(right):这两个指针分别表示当前考察的窗口的左右边界。初始时,两者都指向字符串s的开始位置。

  3. 扩大窗口

    1. 如果窗口中某个字符的数量达到了t中该字符所需的数量,则认为该字符已被“完全覆盖”。

    2. 移动右指针right来扩大窗口,每次右移一位,并更新windowCounts中对应字符的计数。

  4. 收缩窗口

    1. 在收缩的过程中,如果移除的字符是t中的必需字符,且其数量减少到小于t中所需的数量,则更新formed,表示窗口不再满足条件。

    2. 当窗口已包含所有t中的字符(即formed(用来跟踪当前窗口中完全匹配t中所有字符所需的唯一字符数)等于freqMap的大小时),尝试通过移动左指针left来收缩窗口,以找到可能的最小窗口。

  5. 更新最小窗口

    1. 在每次窗口变动(扩大或收缩)后,如果当前窗口满足所有t中字符的要求(即formed等于freqMap的大小),就比较并更新保存最小窗口的变量(如窗口大小、起始位置等)。

  6. 结果输出

    1. 最终,根据保存的最小窗口信息输出最小窗口子串。如果没有找到满足条件的窗口,则返回空字符串。

这种方法的优势在于它通过动态地调整窗口的大小来寻找最小满足条件的窗口,同时利用哈希表来有效地追踪和更新窗口内字符的情况,从而避免了不必要的字符串操作,提高了算法的效率。

3. 代码实现

3.1 暴力求解

3.2 滑动窗口

4. 相关复杂度分析

4.1 暴力求解

  • 时间复杂度

  1. 初始化tCharMap:首先遍历字符串t中的所有字符,这一步的时间复杂度为O(m),其中m是字符串t的长度。向HashMap中插入元素的平均时间复杂度为O(1),因此这一步的时间复杂度为O(m)。

  2. 寻找最小窗口:通过嵌套循环遍历字符串s,试图找到包含t中所有字符的最小窗口。对于s中的每一个字符(外层循环),它会迭代剩余的字符(内层循环)来找到这样的窗口。这导致了最坏情况下的时间复杂度为O(n^2),其中n是字符串s的长度。这是因为对于s中的每个字符,我们在最坏的情况下可能需要扫描它右侧的每个其他字符。

  3. 内层循环中的操作:内层循环中进行了检查tCharMapCopy中是否存在字符、更新映射和复制HashMap(tCharMapCopy = new HashMap<>(tCharMap))等操作。HashMap的复制操作是O(m),因为它必须复制原始映射中的所有条目。这个操作对于外层循环的每次迭代都会进行。对于s中的每个字符,检查和更新HashMap的平均时间复杂度是O(1)。

  4. 综合考虑这些操作,主导因素是嵌套循环结构,使得整体时间复杂度为O(n^2 × m)

  • 空间复杂度

  1. tCharMap的空间复杂度:这个HashMap存储了字符串t中每个字符及其出现的次数。因此,它的空间复杂度取决于t中不同字符的数量。在最坏的情况下,如果t中每个字符都不同,这个映射的空间复杂度是O(m),其中m是字符串t的长度。

  2. tCharMapCopy的空间复杂度:在外层循环的每次迭代中,我们都创建了HashMap tCharMap的一个副本。尽管这些副本是依次创建的,而不是同时存在的,但每个副本的空间复杂度也是O(m),因为它包含了t中所有不同字符的一个完整映射。

  3. 结果字符串ret的空间复杂度:这取决于找到的最小窗口的大小。在最坏的情况下,如果整个s是最小窗口,那么这个字符串的空间复杂度为O(n),其中n是字符串s的长度。因此,总的空间复杂度是由这三部分组成的,即O(m) + O(m) + O(n) = O(m + n)。这表明空间复杂度与输入字符串s和t的长度线性相关,主要是因为需要存储字符及其出现次数的映射以及可能的最小窗口子串。总结来说,此代码的空间复杂度主要受到两个因素的影响:一是存储t字符及其频率的映射,二是在查找过程中创建的映射副本以及存储结果子串。

  4. 因此,空间复杂度为O(m + n)

4.2 滑动窗口

  • 时间复杂度

  1. 哈希表操作:哈希表的插入、删除和查找操作的平均时间复杂度为O(1)。因此,尽管在遍历字符串和调整窗口的过程中进行了多次这些操作,它们的时间复杂度在整个算法的上下文中是线性的。综合以上各点,整个算法的总时间复杂度为O(n + m),这反映了需要分别遍历字符串s和t各一次,而对哈希表的操作平均来说是常数时间的。

  2. 窗口内数据更新:虽然左指针left的移动看起来在内部循环中进行,但每个字符在整个算法中由左指针和右指针各访问一次,因此这部分操作的总时间复杂度仍然是O(n)。

  3. 遍历字符串s:算法通过移动右指针right来遍历字符串s一次,对于字符串s中的每个字符,可能需要进行一次哈希表的更新操作。这部分的时间复杂度为O(n),其中n是字符串s的长度。

  4. 初始化频率表:遍历字符串t以构建字符频率表的时间复杂度为O(m),其中m是字符串t的长度。

  • 空间复杂度

  1. 答案记录:用于存储最小窗口的起始位置和长度的变量是固定大小的,可以忽略不计。因此,整个算法的总空间复杂度主要由哈希表决定,为O(m)。总结来说,优化后的滑动窗口算法的时间复杂度是O(n + m),空间复杂度是O(m),这反映了算法与输入字符串s的长度和字符串t中不同字符的数量线性相关。

  2. 频率表和窗口计数表:这两个哈希表存储了字符串t中所有唯一字符的计数,以及当前窗口中字符的计数。空间复杂度取决于字符串t中不同字符的数量。在最坏的情况下,如果t中每个字符都不同,这部分的空间复杂度为O(m),其中m是字符串t中唯一字符的数量。

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

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

相关文章

ng : 无法加载文件 C:\Program Files\nodejs\node_global\ng.ps1, 因为在此系统上禁止运行脚本

ng : 无法加载文件 C:\Program Files\nodejs\node_global\ng.ps1&#xff0c;因为在此系统上禁止运行脚本 今天在VSCode中运行ng serve --port 8081运行基于Angular的项目时&#xff0c;报错了&#xff0c;错误如下图所示&#xff1a; 解决方法&#xff1a; 按照下图的5步即…

智能汽车行业产业研究报告:4D成像毫米波雷达—自动驾驶最佳辅助

今天分享的是智能汽车系列深度研究报告&#xff1a;《智能汽车行业产业研究报告&#xff1a;4D成像毫米波雷达—自动驾驶最佳辅助》。 &#xff08;报告出品方&#xff1a;开源证券&#xff09; 报告共计&#xff1a;43页 视觉感知最佳辅助——4D 成像毫米波雷达 感知是自动…

解决‘vue‘ 不是内部或外部命令,也不是可运行的程序(设置全局变量)

发现是没有执行&#xff1a; npm install -g vue/cli 但是发现还是不行 此时&#xff0c;我们安装了 Vue CLI&#xff0c;但是在运行 vue ui 命令时出现了问题。这通常是因为全局安装的 Vue CLI 的路径没有被正确地添加到系统的环境变量中。 可以尝试以下几种方法来解决这个问…

如何把华为手机上的数据转移到荣耀手机上?

方法/步骤 点击并进入华为手机&#xff08;旧手机&#xff09;的【手机克隆】应用&#xff0c;选择【这是旧设备】&#xff1b; 点击并进入荣耀手机&#xff08;新手机&#xff09;的【换机克隆】应用&#xff0c;选择【这是新设备】&#xff1b; 荣耀手机&#xff08;新…

《VulnStack》ATTCK-1

title: 《VulnStack》ATT&CK-1 date: 2024-01-29 14:53:49 updated: 2024-02-14 18:55:49 categories: WriteUp&#xff1a;Cyber-Range excerpt: 主机发现、端口扫描&#xff0c;服务探测&#xff0c;操作系统探测、nmap 漏洞库扫描、网站首页信息泄露、msf 渗透与信息收集…

源码推荐:hello-algo @ github

github https://github.com/krahets/hello-algo 本项目旨在创建一本开源、免费、对新手友好的数据结构与算法入门教程。全书采用动画图解&#xff0c;结构化地讲解数据结构与算法知识&#xff0c;内容清晰易懂&#xff0c;学习曲线平滑。算法源代码皆可一键运行&#xff0c;支…

【IDEA】新建Spring Initializr项目,选择java版本只有是17和21问题的解决方法

新建Spring Initializr项目时&#xff0c;选择java版本只有是17和21 2. 将https://start.spring.io修改为阿里云的服务器路径&#xff1a;https://start.aliyun.com 能够选择Java8、11等版本

【Linux笔记】动静态库的封装和加载

一、静态库的封装 我们在学习C语言阶段其实就已经知道一个可执行程序的形成过程分为预处理、编译、汇编、链接这四个阶段&#xff0c;而且也知道我们程序中使用的各种库其实是在链接的阶段加载的。 可我们那时候并不知道库是怎么被加载的&#xff0c;或者库是怎么形成的&…

项目访问量激增该如何应对

✨✨ 欢迎大家来到喔的嘛呀的博客✨✨ &#x1f388;&#x1f388;希望这篇博客对大家能有帮助&#x1f388;&#x1f388; 目录 引言 一. 优化数据库 1.1 索引优化 1.2 查询优化 1.3 数据库设计优化 1.4 事务优化 1.5 硬件优化 1.6 数据库配置优化 二. 增加服务器资源…

监督学习:从数据中挖掘模式的引导

目录 前言1 定义2 举例说明3 回归问题4 分类问题结论 前言 监督学习是机器学习领域中的一种重要方法&#xff0c;通过给模型提供带有标签的训练数据&#xff0c;使其能够学习输入与输出之间的关系。这种学习方式在各个领域都有广泛的应用&#xff0c;从垃圾邮件过滤到医学诊断…

ClickHouse--08--SQL DDL 操作

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 SQL DDL 操作1 创建库2 查看数据库3 删除库4 创建表5 查看表6 查看表的定义7 查看表的字段8 删除表9 修改表9.1 添加列9.2 删除列9.3 清空列9.4 给列修改注释9.5 修…

【AI视野·今日CV 计算机视觉论文速览 第298期】Fri, 26 Jan 2024

AI视野今日CS.CV 计算机视觉论文速览 Fri, 26 Jan 2024 Totally 71 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Computer Vision Papers Multimodal Pathway: Improve Transformers with Irrelevant Data from Other Modalities Authors Yiyuan Zhang, Xiaohan …

ARP请求的构造过程

ARP请求的构造过程&#xff1a; ARP请求的构造&#xff1a; 当设备A&#xff08;发起者&#xff09;想要与设备B&#xff08;目标&#xff09;通信&#xff0c;但它只知道设备B的IP地址&#xff08;例如&#xff0c;192.168.1.2&#xff09;&#xff0c;而不知道其MAC地址。设备…

OpenGL-ES 学习(4)---- OpenGL-ES 坐标体系

坐标体系 我们知道 OpenGL -ES 坐标系中每个顶点的 x&#xff0c;y&#xff0c;z 坐标都应该在 -1.0 到 1.0 之间&#xff0c;超出这个坐标范围的顶点都将不可见。 将一个物体&#xff08;图像&#xff09;渲染到屏幕上&#xff0c;通常经过将物体坐标转换为标准化设备坐标&am…

顺序表(上)

1.顺序表的概念 顺序表&#xff08;Sequential List&#xff09;是一种基本的数据结构&#xff0c;它是一种线性表的存储结构。线性表是一种数据元素的有限序列&#xff0c;元素之间存在顺序关系。 线性表&#xff1a;线性表&#xff08; linearlist &#xff09;是n个具有相…

PHP毕业设计图片分享网站76t17

图片分享网站主要是为了提高工作人员的工作效率和更方便快捷的满足用户&#xff0c;更好存储所有数据信息及快速方便的检索功能&#xff0c;对系统的各个模块是通过许多今天的发达系统做出合理的分析来确定考虑用户的可操作性&#xff0c;遵循开发的系统优化的原则&#xff0c;…

【树莓派系统的位数】

要区分 ARM 架构下载的版本是 32 位还是 64 位&#xff0c;可以执行以下步骤&#xff1a; 执行以下命令来检查 Raspberry Pi 的 CPU 类型&#xff1a; uname -m如果返回的结果是 aarch64&#xff0c;则表示您的 Raspberry Pi 是 64 位的 ARM 架构。如果返回的结果是 armv7l&a…

代码随想录 Leetcode134. 加油站

题目&#xff1a; 代码(首刷看解析 2024年2月15日&#xff09;&#xff1a; class Solution { public:int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {int curSum 0;int sum 0;int startIndex 0;for (int i 0; i < gas.size(); i)…

18 19 SPI接口的74HC595驱动数码管实验

1. 串行移位寄存器原理&#xff08;以四个移位寄存器为例&#xff09; 1. 通过移位寄存器实现串转并&#xff1a;一个数据输入端口可得到四位并行数据。 通过给data输送0101数据&#xff0c;那么在经过四个时钟周期后&#xff0c;与data相连的四个寄存器的输出端口得到了0101…

高德地图上绘制热力图的方法

百度地图和高德地图的JavaScript API都提供了热力图的绘制方法&#xff0c;都是将热力图作为新的图层&#xff0c;叠加到地图上。但是百度地图的经纬度体系与我们的经纬度存在偏差&#xff0c;高德的与我们相符&#xff0c;应当使用高德地图JavaScript API。 因为是JavaScript…