1. 长度最小的子数组
题目要求:给定一个含有 n 个正整数的数组和一个整数 target,找出和大于等于 target 的长度最小的连续子数组。
解法一:暴力枚举 三层循环,两层遍历每个可能的区间,判断是否符合要求。另需一层循环去遍历当前区间统计 sum,时间复杂度是 O(N^3)。
优化解法一: 由于题目说明只含正整数,区间变大的过程 sum 一定变大(单调性)。当 sum 已经达到 target,就不必继续增长 len 遍历后面的区间,因为虽然 sum 符合但 len 更长,不会作为答案。sum 的统计可以不用遍历,每增加一个长度就累加到 sum 中。省略一个循环后,时间复杂度优化为 O(N^2)。
解法二:滑动窗口 滑动窗口即同向双指针。两个指针都向同一方向移动,很像一个窗口在数组中移动。因为单调性(区间越长求和越大),双指针模拟时方向总是向右,不会回退。单调性 + 同向双指针 = 滑动窗口。 本质是总结上面的优化解法:使用左右指针维护窗口,右指针扩展窗口直到 sum >= target,左指针收缩窗口以寻找最小长度。
时间复杂度: 代码层面看似两层循环 O(N^2),实际上 left 最多移动 n 次,right 最多移动 n 次,总复杂度为 O(N)。
2. 无重复字符的最长子串
题目要求:找到字符串中不含有重复字符的最长子串的长度。
解法一:暴力枚举 + 哈希表 每个区间遍历一遍,用哈希表统计。每次 i 变化都要重置哈希表,时间复杂度较高。
优化解法一: 当 j 遍历到重复字符时,如果让 i++,j 往回走重新遍历,效率低。例如固定 i 在 d,枚举到 deabca 遇到重复 a,统计长度 6。如果 i++ 到 e,j 也回去,最终会在 eabca 遇到重复 a,长度变短。应避免无用功,让 i 一直 ++,直到移出重复值 a,才有可能比最初长度长。跳过重复值 a 后,j 不需要回退,因为 a 是第一个重复值,此时围成的区间没有重复值了。
解法二:滑动窗口 + 哈希表 规律在于让 j 不需回退,推导出是同向双指针。更新结果是个关键问题,建议每次循环都更新一下长度,避免全是不重复字符导致未统计的情况。
3. 最大连续 1 的个数 III
题目要求:给定二进制数组 nums 和一个整数 k,允许翻转最多 k 个 0,返回连续 1 的最大个数。
注意:题目的翻转并不需要真的翻转,可以通过控制 K 的增减间接翻转。
解法一:暴力枚举 固定 i,活动 j,遍历所有区间,找出区间内 0 的个数 <= K 且区间长度最长的子串。时间复杂度 O(N*N)。
优化解法一: 利用 int zero_count 来记数。j 不需要回退,j 前面的区间总是有超过 K 个 0,回退 j 无论如何也超不过最初长度。正确的做法是让 i 移动,直到区间内 0 的个数 <= K。不需要移动到一个 0 都没有,只要满足条件即可。
解法二:滑动窗口 直接看代码逻辑:维护窗口内 0 的计数,当超过 K 时移动左指针。
4. 将 x 减到 0 的最小操作数
题目要求:从数组两端移除元素使总和减少 x,求最小操作数。
易错点: 不要直接 sort 排序。原本是乱序的正整数数组,可能头尾两个数字就满足 x 了,排序后位置发生变化,就不一定是 2 次。例如 x=7,头尾是 2,5,排序前取头尾只需 2 次,排序后最短可能是 3 次。
反向思考解题: 正向思考涉及频繁统计头尾大小,代码难写。反向思考:题目要求最小 N 次出头尾操作数,使得所有移出的头尾的值 == x,也就是中间剩余部分的值累加 == sum - x。我们只要求一个最长的子串 len(移出一个等于长度减一,所以长度就是最终的操作数),让这个子串的所有值累加 == sum - x。最后返回数组 size - 子串 len。 问题转化为求值为 sum - x 的最长子串,使用滑动窗口解法。
5. 水果成篮
题目要求:找出只含有两个不同数字的最长子数组。
解法一:暴力枚举 + 哈希表 固定 i,移动 j,遍历所有区间,找到哈希表长度为 2 的最大子串。时间复杂度 O(N*N)。
解法一优化: 有些区间 j 不需要回退。left++ 后,当前区间只有两种可能:种类减少或种类不变。若种类减少,j 可继续向后找新种类;若种类不变(仍 >2 种),j 不能动。
解法二:滑动窗口 对优化解法的总结归纳。代码层面时间复杂度 O(N*N),实际 O(N)。空间复杂度 O(1)。 静态数组也可以实现。


