滑动窗口算法是较为入门题目的算法,一般是一些有规律数组问题的最优解,也就是说,如果一个数组问题可以用动态规划解,但又可以使用滑动窗口解决,那么往往滑动窗口的效率更高。
双指针也并不局限在数组问题,像链表场景的 “快慢指针” 也属于双指针的场景,其快慢指针滑动过程中本身就会产生一个窗口,比如当窗口收缩到某种程度,可以得到一些结论。
因此掌握滑动窗口非常基础且重要,接下来按照我的经验给大家介绍这个算法。
精读
滑动窗口使用双指针解决问题,所以一般也叫双指针算法,因为两个指针间形成一个窗口。
什么情况适合用双指针呢?一般双指针是暴力算法的优化版,所以:
如果题目较为简单,且是数组或链表问题,往往可以尝试双指针是否可解。
如果数组存在规律,可以尝试双指针。
如果链表问题限制较多,比如要求 O(1) 空间复杂度解决,也许只有双指针可解。
也就是说,当一个问题比较有规律,或者较为简单,或较为巧妙时,可以尝试双指针(滑动窗口)解法。
我们还是拿例子说明,首先是两数之和。
两数之和
两数之和是一道简单题,实际上和滑动窗口没什么关系,但为了引出三数之和,还是先讲这道题。题目如下:
给定一个整数数组
nums
和一个整数目标值target
,请你在该数组中找出 和为目标值target
的那 两个 整数,并返回它们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
暴力解法就是穷举所有两数之和,发现和为 target
结束,显然这种做法有点慢,我们换一种思路。
由于可以用空间换时间,又只有两个数,我们可以对题目进行转化,即通过一次遍历,将 nums
每一项都减去 target
,然后找到后面任意一项值为前面的结果,即表示它们和为 target
。
可以用哈希表 map
加速查询,即将每一项 target - num
作为 key,如果后面任何一个 num
作为 key 可以在 map
中找到,则得解,且上一个数的原始值可以存在 map
的 value 中。这要仅需遍历一次,时间复杂度为 O(n)。
之所以说这道题,是因为这道题是单指针,即只有一个指针在数组中移动,并配合哈希表快速求解。对于稍微复杂的问题,单指针就不够了,需要用双指针解决(一般来说不会用到三或以上指针),那复杂点的题目就是三数之和了。
三数之和
三数之和是一道中等题,别以为只是两数之和的加强版,其思路完全不同。题目如下:
给你一个包含
n
个整数的数组nums
,判断nums
中是否存在三个元素a
,b
,c
,使得a + b + c = 0
?请你找出所有和为0
且不重复的三元组。
由于超过了两个数,所以不能像双指针一样求解了,因为即便用了哈希表存储,也会在遍历时遇到 “两数之和” 的问题,而哈希表方案无法继续嵌套使用,即无法进一步降低复杂度。
为了降低时间复杂度,我们希望只遍历一次数组,这就需要数组满足一定条件我们才能用滑动窗口,所以我们对数组进行排序,使用快排的时间复杂度为 O(nlogn),时间复杂度已超出两数之和,不过因为题目复杂,这个牺牲是无法避免的。
假设从小到大排序,那我们就拿到一个递增数组了,此时经典滑动窗口方法就可用了!怎么滑动呢?首先创建两个指针,分别叫 left
与 right
,通过不断修改 left
与 right
,让它们在数组间滑动,这个窗口大小就是符合题目要求的,当滑动完毕时ÿ