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


串联所有单词的子串


串联所有单词的子串


题目描述


题目解析



算法原理


以示例一为例,一定要记得,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 中字符的频次

因为



相关推荐
一起养小猫几秒前
LeetCode100天Day2-验证回文串与接雨水
java·leetcode
YoungHong19923 分钟前
面试经典150题[073]:从中序与后序遍历序列构造二叉树(LeetCode 106)
leetcode·面试·职场和发展
csbysj20203 分钟前
XML 技术
开发语言
最晚的py3 分钟前
聚类的评估方法
人工智能·算法·机器学习
清晓粼溪4 分钟前
Java登录认证解决方案
java·开发语言
小徐Chao努力5 分钟前
Go语言核心知识点底层原理教程【变量、类型与常量】
开发语言·后端·golang
锥锋骚年6 分钟前
go语言异常处理方案
开发语言·后端·golang
沐知全栈开发6 分钟前
JSP 自动刷新技术详解
开发语言
业精于勤的牙7 分钟前
浅谈:算法中的斐波那契数(五)
算法·leetcode·职场和发展
特立独行的猫a8 分钟前
C++使用Boost的Asio库优雅实现定时器与线程池工具类
开发语言·c++·线程池·定时器·boost·asio