长度最小的子数组(medium)
解法一(暴力求解)(不会超时,可以通过)
算法思路 :枚举从每一个位置 开始往后,无重复字符的子串可以到达什么位置。找出其中长度最大的即可。在往后寻找无重复子串能到达的位置时,可以利用哈希表统计出字符出现的频次,来判断什么时候子串出现了重复元素。

解法二(滑动窗口)
算法思路 :由于此问题分析的对象是一段连续的区间 ,因此可以考虑滑动窗口的思想来解决这道题。让滑动窗口满足:从 i 位置开始,窗口内所有元素的和小于 target (那么当窗口内元素之和第一次大于等于目标值的时候,就是 i 位置开始,满足条件的最小长度)。
做法:将右端元素划入窗口中,统计出此时窗口内元素的和:
-
如果窗口内元素之和大于等于 target :更新结果,并且将左端元素划出去的同时继续判断是否满足条件并更新结果(因为左端元素可能很小,划出去之后依旧满足条件)
-
如果窗口内元素之和不满足条件: right++ ,另下一个元素进入窗口。
为何滑动窗口可以解决问题,并且时间复杂度更低?
算法思路:
-
这个窗口寻找的是:以当前窗口最左侧元素(记为 left1 )为基准,符合条件的情况。也就是在这道题中,从 left1 开始,满足区间和 sum >= target 时的最右侧(记为 right1 )能到哪里。
-
我们既然已经找到从 left1 开始的最优的区间,那么就可以大胆舍去 left1 。但是如果继续像方法一 样,重新开始统计第二个元素( left2 )往后的和,势必会有大量重复的计算(因为我们在求第一段区间的时候,已经算出很多元素的和了,这些和是可以在计算下次区间和的时候用上的)。
-
此时, right1 的作用就体现出来了,我们只需将 left1 这个值从 sum 中剔除。从 right1 这个元素开始,往后找满足 left2 元素的区间(此时 right1 也有可能是满足的,因为 left1 可能很小。 sum 剔除掉 left1 之后,依旧满足大于等于 target )。这样我们就能省掉大量重复的计算。
-
这样我们不仅能解决问题,而且效率也会大提升
时间复杂度:虽然代码是两层循环,但是我们的 left 指针和 right 指针都是不回退的,两者最多都往后移动 n 次。因此时间复杂度是 O(N)

无重复字符的最长子串(medium)
解法一(暴力求解)(不会超时,可以通过)
算法思路 :枚举从每一个位置 开始往后,无重复字符的子串可以到达什么位置。找出其中长度最大的即可。在往后寻找无重复子串能到达的位置时,可以利用哈希表统计出字符出现的频次,来判断什么时候子串出现了重复元素

解法二(滑动窗口)
算法思路 :研究的对象依旧是一段连续的区间,因此继续使用滑动窗口思想来优化。让滑动窗口满足:窗口内所有元素都是不重复的。
做法:右端元素 ch 进入窗口的时候,哈希表统计这个字符的频次:
-
如果这个字符出现的频次超过 1 ,说明窗口内有重复元素,那么就从左侧开始划出窗口,直到 ch 这个元素的频次变为 1 ,然后再更新结果
-
如果没有超过 1 ,说明当前窗口没有重复元素,可以直接更新结果

最大连续 1 的个数 III(medium)
1004. 最大连续1的个数 III - 力扣(LeetCode)
解法一,暴力枚举
思路:尝试所有可能的子数组,统计每个子数组中 0 的个数,如果符合条件(0 的个数 ≤k),就更新最大长度。

解法二(滑动窗口)
算法思路:不要去想怎么翻转,不要把问题想的很复杂,这道题的结果无非就是一段连续的 1 中间塞了 k 个 0 嘛。因此,我们可以把问题转化成:求数组中一段最长的连续区间,要求这段区间内 0 的个数不超过 k 个。既然是连续区间,可以考虑使用滑动窗口来解决问题。
算法流程
a. 初始化一个大小为 2 的数组就可以当做哈希表 hash 了;初始化一些变量 left = 0 , right = 0 , ret = 0
b. 当 right 小于数组大小的时候,一直下列循环:
i. 让当前元素进入窗口,顺便统计到哈希表中;
ii. 检查 0 的个数是否超标:
如果超标,依次让左侧元素滑出窗口,顺便更新哈希表的值,直到 0 的个数恢复正常;
iii. 程序到这里,说明窗口内元素是符合要求的,更新结果;
iv. right++ ,处理下一个元素;
c. 循环结束后, ret 存的就是最终结果。


将 x 减到 0 的最小操作数 (medium)
1658. 将 x 减到 0 的最小操作数 - 力扣(LeetCode)
解法一,暴力求解
将"从两端缩减"转化为"寻找中间最长的子数组",直接把问题转化为了经典的"和为定值的最长子数组"问题
思路:
计算数组总和
totalSum。我们的目标是找一个连续的子数组,其和为
target = totalSum - x。如果找不到这样的子数组,返回 -1。
如果找到了,假设这个子数组长度为
L,那么移除的次数就是n - L。为了让移除次数最少,就要让L(中间保留的长度)最长。

解法二,滑动窗口
算法思路 :题目要求的是数组左端+右端 两段连续的、和为 x 的最短数组,信息量稍微多一些,不易理清思路;我们可以转化成求数组内一段连续的、和为 sum(nums) - x 的最长数组。此时,就是熟悉的滑动窗口问题了。
算法流程 :
a. 转化问题:求 target = sum(nums) - x 。如果 target < 0 ,问题无解;
b. 初始化左右指针 l = 0 , r = 0 (滑动窗口区间表示为 [l, r) ,左右区间是否开闭很重要,必须设定与代码一致),记录当前滑动窗口内数组和的变量 sum = 0 ,记录当前满足条件数组的最大区间长度 maxLen = -1 ;
c. 当 r 小于等于数组长度时,一直循环:
i. 如果 sum < target ,右移右指针,直至变量和大于等于 target ,或右指针已经移到头;
ii. 如果 sum > target ,右移左指针,直至变量和小于等于 target ,或左指针已经移到头;
iii. 如果经过前两步的左右移动使得 sum == target ,维护满足条件数组的最大长度,并让下个元素进入窗口;
d. 循环结束后,如果 maxLen 的值有意义,则计算结果返回;否则,返回 -1 。

水果成篮(medium
解法(滑动窗口)
算法思路 :这道题讲的就是在一个数组中,找一个 最长的连续子数组 ,这个子数组里最多只能包含 两种不同的数字(水果种类) 。研究的对象是一段连续的区间,可以使用滑动窗口思想来解决问题。让滑动窗口满足:窗口内水果的种类只有两种。
做法:右端水果进入窗口的时候,用哈希表统计这个水果的频次。这个水果进来后,判断哈希表的大小:
-
如果大小超过 2:说明窗口内水果种类超过了两种。那么就从左侧开始依次将水果划出窗口,直到哈希表的大小小于等于 2,然后更新结果;
-
如果没有超过 2,说明当前窗口内水果的种类不超过两种,直接更新结果 ret。
算法流程 :
a. 初始化哈希表 hash 来统计窗口内水果的种类和数量;
b. 初始化变量:左右指针 left = 0,right = 0,记录结果的变量 ret = 0;
c. 当 right 小于数组大小的时候,一直执行下列循环:
i. 将当前水果放入哈希表中;
ii. 判断当前水果进来后,哈希表的大小:
如果超过 2:
a. 将左侧元素滑出窗口,并且在哈希表中将该元素的频次减一;
b. 如果这个元素的频次减一之后变成了 0,就把该元素从哈希表中删除;
c. 重复上述两个过程,直到哈希表中的大小不超过 2;
iii. 更新结果 ret;
iv. right++,让下一个元素进入窗口;
d. 循环结束后,ret 存的就是最终结果。
使用unordered map容器来实现
我们直接利用 hash.size() 直接代表"当前窗口内水果的种类数"。因为每当某种水果计数归零时,我们立刻用 erase 把它从哈希表中删掉,所以 size() 永远精准地等于当前窗口里实际存在的不同水果数量。

为什么是 unordered_map 而不是别的?
核心选择标准:看要存"单数"还是"双数"
选择哪个容器,本质上只取决于两个问题:
问题一:我需要"键"和"值"配对吗?(Map 还是 Set)
| 需求 | 用什么 | 举例 |
|---|---|---|
| 只想知道 "这个东西有没有出现过" | unordered_set |
给一个数组,去重。只需要知道 3 在不在里面。 |
| 想知道 "这个东西出现了几次" / "这个东西对应什么数据" | unordered_map |
这道题:水果 1 出现了几次?需要存 {1: 3}。 |
问题二:允许重复的键吗?(普通还是 Multi)
| 需求 | 用什么 | 举例 |
|---|---|---|
| 键 不允许重复(唯一的) | 去掉 multi,如 unordered_set / unordered_map |
统计每种水果的数量,苹果只能对应一个数量。 |
| 键 允许重复(一个键对应多个值) | 加上 multi,如 unordered_multiset / unordered_multimap |
一个班级里有多个人叫"张三",我想存储所有叫"张三"的学生信息。 |
我们一步一步套用上面的标准:
需求 :我需要记录"水果种类 A 在当前窗口里出现了 几次"。
这需要存 键值对 (水果种类 -> 出现次数),所以选 map 家族,不选 set。
每种水果(键)在窗口里只对应 一个 出现次数(值),键是唯一的,不需要重复键,所以去掉 multi。结论 :只能是 unordered_map。
数组模拟哈希表来实现

找到字符串中所有字母异位词(medium)
438. 找到字符串中所有字母异位词 - 力扣(LeetCode)
解法(滑动窗口 + 哈希表)
算法思路:
因为字符串 p 的异位词的长度一定与字符串 p 的长度相同,所以我们可以在字符串 s 中构造一个长度为与字符串 p 的长度相同的滑动窗口,并在滑动中维护窗口中每种字母的数量;
当窗口中每种字母的数量与字符串 p 中每种字母的数量相同时,则说明当前窗口为字符串 p 的异位词;
因此可以用两个大小为 26 的数组来模拟哈希表,一个来保存 s 中的子串每个字符出现的频次,另一个保存 p 中每个字符出现的频次,通过比较两个数组是否相等来判断异位词。

串联所有单词的子串(hard)
三个三个一组划分,可以把串联所有单词的子串这个复杂复杂问题转换为查找字母异位词

这里与上一道题的区别有三处:
哈希表:此处使用unordered_map
left与right指针的移动步长:每个单词的长度len
滑动窗口执行次数:len(即每个单词的长度)。解释:由于是三个三个一组,窗口的起始位置有三种可能(三个三个一组进行读取,但是原s里可能混有其他无关字母,所以导致了有三种情况)
这第四种情况与第一种重复了,所以总的情况只有三种


最小覆盖子串(hard)
解法(滑动窗口 + 哈希表)
算法思路:研究对象是连续的区间,因此可以尝试使用滑动窗口的思想来解决。如何判断当前窗口内的所有字符是符合要求的呢?我们可以使用两个哈希表,其中一个将目标串的信息统计起来,另一个哈希表动态的维护窗口内字符串的信息。当动态哈希表中包含目标串中所有的字符,并且对应的个数都不小于目标串的哈希表中各个字符的个数,那么当前的窗口就是一种可行的方案。
算法流程 :
a. 定义两个全局的哈希表: 1 号哈希表 hash1 用来记录子串的信息, 2 号哈希表 hash2 用来记录目标串 t 的信息;
b. 实现一个接口函数,判断当前窗口是否满足要求:
i. 遍历两个哈希表中对应位置的元素:
-
如果 t 中某个字符的数量大于窗口中字符的数量,也就是 2 号哈希表某个位置大于 1 号哈希表。说明不匹配,返回 false ;
-
如果全都匹配,返回 true 。
主函数中:
a. 先将 t 的信息放入 2 号哈希表中;
b. 初始化一些变量:左右指针: left = 0,right = 0 ;目标子串的长度: len = INT_MAX ;目标子串的起始位置: retleft ;(通过目标子串的起始位置和长度,我们就能找到结果)
c. 当 right 小于字符串 s 的长度时,一直下列循环:
i. 将当前遍历到的元素扔进 1 号哈希表中;
ii. 检测当前窗口是否满足条件:
-
如果满足条件:
a. 判断当前窗口是否变小。如果变小:更新长度 len ,以及字符串的起始位置 retleft ;
b. 判断完毕后,将左侧元素滑出窗口,顺便更新 1 号哈希表;
c. 重复上面两个过程,直到窗口不满足条件;
iii. right++ ,遍历下一个元素;
d. 判断 len 的长度是否等于 INT_MAX :
i. 如果相等,说明没有匹配,返回空串;
ii. 如果不相等,说明匹配,返回 s 中从 retleft 位置往后 len 长度的字符串。