【优选算法 — 滑动窗口】串联所有单词的子串 & 最小覆盖子串


串联所有单词的子串


串联所有单词的子串


题目描述


题目解析



算法原理


以示例一为例,一定要记得,words中的每一个字符串长度相同,所以我们可以根据 words 中的每一个字符串的长度length,将 s 这个字符串以 length 个为一组来进行划分。

通过这样的转换,就变成了上一篇优选算法的问题:"找出字符串中所有字母异位词"

所以这道题的示例一:

解法:滑动窗口 + 哈希表


两道题不同之处:


1. 哈希表


在438题中,我们是通过数组来模拟哈希表,因为哈希表中的每一个元素 key 是一个个字符;

但是在这题是一个个字符串,映射关系是 HashMap<String, int >,表示字符串及出现次数 ;


2. left 和 right 指针的移动


刚开始,我们的left 和 right 两个指针,都指向 s 字符串的第一个元素:

但是我们在每次入窗口操作时,都要把三个字符组成的字符串划入哈希表中,所以 left,right 移动的步长,是 words 中字符串元素的长度。

在这题中,我们移动 right ,让第二个字符串进入哈希表:


3. 滑动窗口的执行次数


因为我们是把三个字符 看作是一个字符,所以划分方法有很多种,如下面的这三种:

  • 因此滑动窗口的次数,就是能列举出来的上图的三种情况;
  • 因为上面三种情况,分别以 s 的第一,第二,第三个字符为起点,把长度3的字符串看成一个字符;
  • 那接下来就是以第四个字符为起点,那这种情况就变回了上面的第一种划分方法,只是少了第一个字符串 "bar" ;
  • 同理,后面再以第五,第六,第七个 .......s 字符串的字符为起点,结果都能合并为上面三种划分情况

编写代码


定义哈希表 hash1,用来保存 wors 中所有单词(字符串元素)的频次

把 words 的每一个字符串元素存入 hash1

滑动窗口的执行次数(外层 for 循环)


完善滑动窗口的主逻辑;


left 和 right 指针不是从0开始,而是从 i 开始,如果 left 和 right 从0开始,就会重新遍历很多已经遍历过的字符串(内层 for 循环);

count 用来记录有效字符串的个数:


注意,求数组长度和字符串长度,分别使用的是 length 和 length()


接下来有一个小细节:


所以对于上面的这两种情况,right 指针能到的极限位置就是上面两种情况 right 指向的位置,只要 right 到达这个位置,就不能再往后移动了,所以我们修改一下代码:


定义一个哈希表 hash2,保存窗口内所有单词的频次;


进窗口,维护 count;此时,input 存的字符串,就是要进窗口的字符串:


进窗口


方法一:

执行用时


方法二

执行用时


进完窗口,要看一下 key 为 input 的哈希元素,如果 hash2 的 val 是否小于 hash1 ,则 count++


  • 但是有一个问题,如果在 hash1 中并没有 key 为 input 的元素,则会报错;
  • 因为调用哈希表中的 get(key),如果对应的 val 为0,则代表不存在这个映射,get() 此时返回 null
  • 因此我们修改一下代码(如果 hash1 中有 key 为 input 的元素,返回该元素 val,否则返回0;)

判断是否需要出窗口:


注意 m = words.length,表示单词的个数,len 表示 words 中的单词长度。len*m 表示窗口长度。

(如果 right - left +1 > len*m,就出窗口,注意不是一个word 的长度,而是 words所有字符之和)


在 if 中,进行出窗口:


要在出窗口之前,维护 count,和上一次维护 count 的原理一样:


更新结果

完整代码


最小覆盖子串


最小覆盖子串


题目解析

算法原理


每次以一个有效字符开始找,直到找完所有符合条件的字符,返回这个子串,然后开始以下一个有效字符为起点开始遍历:

我们用 hash1 存放 t 字符串的字符,hash1的每一个元素 key 为字符,val 为字符出现次数;

再以 hash2 存放 s 字符串的字符,只要遍历到 s 中的字符str,在hash2.get(str)<hash1.get(str),则是有效字符,凑齐所有 t 中所有字符的字符串,就是一个有效字符串;


进出窗口的原理

那么为了不失一般性,我们以上图的抽象例子,来讲解 left 和 right 组成有效字符串后 ,left 和 right 会出现的情况:

left 向后移动一步 ,从字符 str1走到 str2:


情况一: left 删除的是无效字符


  • 如果 str1 字符在 t 中也有,但是 hash2.get(str1)>hash1.get(str1),则说明 str1并不是一个有效的字符,所以 right 不动;
  • right 不动,是因为新的 left 和 right 组成的字符串依旧是有效的字符串,left 删除的是一个无效字符;
  • 但是 right 一回退,则一定会导致有效字符串变成无效字符串,因为 right 只有在 left 和 right 构成有效字符串才会停下来;

情况二:left 删除的是一个有效字符


  • left ++,如果导致新 left 和 right 组成的字符串从有效字符串,变成无效字符串;
  • 则因为 left 删除的字符,导致 hash2.get(str1)<hash1.get(str1) ;
  • 对于这种情况,只能让 left 停止 ++操作,让 right 往后 ++;
  • right 向后遍历,直到 right 遍历到 str1 的时候停止 ++,这时候 left 和 right 就是下一个有效字符串;

通过两种情况,发现 left 和 right 是同向移动的,所以使用滑动窗口解决该题;


解法:滑动窗口 + 哈希表 (how 很重要,但是 why 最重要)


  • left = 0, right = 0;
  • 进窗口(right ++,对应 key 的字符的 val++);
  • 判断是否出窗口(check(hash1,hash2)==true,说明窗口合法,才出窗口,这和前面的题目都不一样)
  • 根据问题,找到合适的位置更新结果(更新起始位置 left 和最短长度,这样就能还原最短子串)
  • (程序执行到这里,窗口都是合法的,因此更新结果的步骤放到判断和出窗口之间);
  • 出窗口(left 出窗口,直到上面判断中的 check(hash1,hash2)==false);

优化判断条件


上面的判断步骤,在每次循环时,都会对 hash1 和 hash2 进行判断,但是每次判断都需要遍历一次 hash1 和 hash2,这样太耗费时间了,我们可以优化判断这一过程;

我们可以定义一个变量 count,标记有效字符的种类 (之前标记的都是有效字符的个数);

因为在串联所有单词的子串中,对于 hash1 和 hash2 中,元素的 val 必须是一 一 对应的,hash1的key对应的val有多少个,hash2就必须有多少个;


但是这道题:

所以,只要 left 和 right 组成的字符串对应的 hash2,每个 key 的 val 都大于等于 hash1,那么就是一个合法窗口,所以 count 标记的是有效字符的种类

count 只看当前字符是不是有效字符(当前字符val:hash2 >= hash1),而不看当前字符出现的次数,因此统计的是种类,为什么是种类,刚才已经解释过了,因为字符val不是一 一对应的关系


  • 进窗口之后,维护 count:只有两个字符出现的次数相等,count 才+1,否则会统计重复的;
  • 出窗口之前,维护count:只有两个字符出现的次数相等,count 才-1,否则会统计重复的;
  • (因为 count 的出现,只服务于能成为有效字符串的指针移动,出现次数大于的时候,不影响有效字符串;出现次数小于的时候,会从有效字符串变成无效字符串;所以只有等于的时候,才+1)
  • 判断条件(只有 count = 哈希表的元素种类hash.size() 时,而不是等于字符数量;count只有凑齐所有的有效字符,才更新结果,而不关系如何成为有效字符,出现次数多了也不算有效字符)

编写代码


这题可以用字符模拟哈希表(使用容器增删查改为O(1),但是消耗还是很大的)

统计字符串 t 中字符的频次

因为



相关推荐
风_流沙3 分钟前
java 对ElasticSearch数据库操作封装工具类(对你是否适用嘞)
java·数据库·elasticsearch
charlie1145141917 分钟前
C++ STL CookBook
开发语言·c++·stl·c++20
袁袁袁袁满7 分钟前
100天精通Python(爬虫篇)——第113天:‌爬虫基础模块之urllib详细教程大全
开发语言·爬虫·python·网络爬虫·爬虫实战·urllib·urllib模块教程
ELI_He99913 分钟前
PHP中替换某个包或某个类
开发语言·php
Lenyiin14 分钟前
01.02、判定是否互为字符重排
算法·leetcode
m0_7482361121 分钟前
Calcite Web 项目常见问题解决方案
开发语言·前端·rust
倔强的石头10629 分钟前
【C++指南】类和对象(九):内部类
开发语言·c++
鸽鸽程序猿29 分钟前
【算法】【优选算法】宽搜(BFS)中队列的使用
算法·宽度优先·队列
Jackey_Song_Odd30 分钟前
C语言 单向链表反转问题
c语言·数据结构·算法·链表
ProtonBase32 分钟前
如何从 0 到 1 ,打造全新一代分布式数据架构
java·网络·数据库·数据仓库·分布式·云原生·架构