本文是关于字符串的子串问题,就数据结构的形态来说还是属于数组的问题,这里主要是关于使用双指针解决滑动窗口的问题。
涉及 leetcode 的76. 最小覆盖子串、567. 字符串的排列、438. 找到字符串中所有字母异位词和3. 无重复字符的最长子串。
包含子串元素问题
76. 最小覆盖子串、567. 字符串的排列和438. 找到字符串中所有字母异位词这一系列的问题都是关于在字符串中包含子串元素的问题。
题目1
首先来看这些题目567. 字符串的排列:给你两个字符串 s1 和 s2,写一个函数来判断 s2 是否包含 s1 的排列。如果是,返回 true;否则,返回 false。
换句话说,s1 的排列之一是 s2 的子串。
示例1:
输入:s1 = "ab" s2 = "eidbaooo"
输出:true
示例2:
输入:s1= "ab" s2 = "eidboaoo"
输出:false
理解一下1
题目的大概意思就是:给你一个子串 s1,需要判断在 s2 中是否一个子串包含子串 s1 中的所有元素。
基本的思路是:
- 获得子串
s1
的长度Len
; - 按照
Len
的大小,使用左右索引指针组成的固定窗口,在字符串s2
上滑动; - 不断比较窗口内的子串和
s1
的元素是否相同; - 比较的方法可以使用hash表或数组,比较各个元素出现的次数相同即可。
以上的方法需要的时间复杂度为两个字符串长度的乘积,即O(NM)。
想要优化到O(n),则可以使用不固定的滑动窗口来完:
- 首先使用hash表记录子串
s1
中元素出现的次数; - 初始化在
s2
上的左右索引指针为0; - 右索引指针进行向右扩张,不断将字符纳入窗口;
- 判断新加入的字符是否子串
s1
中的,若是则在窗口hash表记录字符及其个数; - 此时若窗口内的子串不满足子串
s1
的元素要求,那么左索引指针右移来缩小窗口范围,并更新相应内容即可。
解题1
以上的思路可能我没有很清楚的表示,不过没关系,我们弄清楚以下几点:
- 移动right扩大窗口时该做什么?:判断新纳入窗口的字符是否是我们需要的,若需要则加入,并增加字符个数valid;
- 何时停止扩大窗口,并移动left缩小窗口?:窗口大小 ≥ 子串大小时;
- 缩小窗口时该干什么?;
- 首先判断此时是否已经满足条件,若满足则直接返回;
- 否则移除left处元素,若是需要的元素,则更新有效值,并删除窗口内的元素。
还是看代码逻辑吧
1 | bool checkInclusion(string s1, string s2) { |
ok,有了以上的代码再看剩下两题几乎是一模一样,就不详细解说了,代码如下:
76. 最小覆盖子串
1 | string minWindow(string s, string t) { |
438. 找到字符串中所有字母异位词
所谓异位词也就是子串重新排列嘛,不用感到新奇。
1 | vector<int> findAnagrams(string s, string p) { |
无重复字符的最长子串
这题其实与以上三题大差不差,只不过这里没有给定子串,而是需要我们求出子串而已。
题目2
首先来看题目3. 无重复字符的最长子串:给定一个字符串 s ,请你找出其中不含有重复字符的最长子串的长度。
示例1:
输入:"abcabcbb"
输出:3
示例2:
输入:s = "bbbbb"
输出:1
理解一下2
题目的大概意思就是:找出字符串中元素不重复的子串的长度,也就是需要子串中的元素不重复,个数要尽可能的多。
保持之前的解题思路,但是这里就不需要needhash
表和valid
值了,因为我们没有需要比较的子串,但是需要一个res
来记录长度:
- 右侧子串向前扩张,将新字符纳入窗口;
- 判断该窗口内加入的该子串是否重复;
- 若重复,则左侧索引指针收缩,直至将重复元素抛出;
- 判断此时的长度与
res
大小,更新最大子串长度。
解题2
很简单吧,看代码更简单。
1 | int lengthOfLongestSubstring(string s) { |
个人收获
之前使用双指针解决链表的时候,用快慢指针来解决一些链表上的逻辑问题;但是在字符串这种类似数组的数据结构中也是可以使用索引来表示指针,解决一些逻辑问题。
滑动窗口的结合hash表既可以来表示区域内的元素数量了,对于一些不要求顺序的子串问题就可以使用该方法解决。