接下来就开始介绍数组操作中另一个重要的方法:滑动窗口。
所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。
在暴力解法中,是一个for循环滑动窗口的起始位置,一个for循环为滑动窗口的终止位置,用两个for循环 完成了一个不断搜索区间的过程。
那么滑动窗口如何用一个for循环来完成这个操作呢。
首先要思考 如果用一个for循环,那么应该表示 滑动窗口的起始位置,还是终止位置。
如果只用一个for循环来表示 滑动窗口的起始位置,那么如何遍历剩下的终止位置?
此时难免再次陷入 暴力解法的怪圈。
所以 只用一个for循环,那么这个循环的索引,一定是表示 滑动窗口的终止位置。
那么问题来了, 滑动窗口的起始位置如何移动呢?
这里还是以题目中的示例来举例,s=7, 数组是 2,3,1,2,4,3,来看一下查找的过程:
最后找到 4,3 是最短距离。
其实从动画中可以发现滑动窗口也可以理解为双指针法的一种!只不过这种解法更像是一个窗口的移动,所以叫做滑动窗口更适合一些。
在本题中实现滑动窗口,主要确定如下三点:
- 窗口内是什么?
- 如何移动窗口的起始位置?
- 如何移动窗口的结束位置?
窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。
窗口的起始位置如何移动:如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了)。
窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。
解题的关键在于 窗口的起始位置如何移动,如图所示:
然后我们利用上面的算法完成如下的题:
#滑动窗口法
target=int(input()) # 输入目标值并将其转换为整数
n=list(map(int,input().split(","))) # 输入一串以逗号分隔的数字,并将其转换为整数数组
l=len(n) # 数组n的长度
left=0 # 子数组的左边界指针
right=0 # 子数组的右边界指针
min_len = float('inf') # 记录当前最小子数组的长度,初始值设为正无穷
cur_sum = 0 # 当前的累加值,用于计算子数组的元素之和
while right < l: # 当右边界指针小于数组长度时循环执行以下操作
cur_sum += n[right] # 将n[right]值加到当前累加值cur_sum中
while cur_sum >= target: # 当当前累加值cur_sum大于等于目标值target时循环执行以下操作
min_len = min(min_len, right - left + 1) # 更新最小子数组的长度
cur_sum -= n[left] # 从当前累加值cur_sum中减去n[left]的值
left += 1 # 左边界指针右移
right += 1 # 右边界指针右移
print(min_len) if min_len != float('inf') else 0 # 输出最小子数组的长度(如果存在),否则输出0
left和right: 指向子数组的左右边界
min_len: 记录当前最小子数组的长度,初始值设为正无穷
cur_sum: 记录当前子数组的元素之和,初始值为0
代码同样使用两个指针left和right来构建一个滑动窗口。right指针向右移动,每次将n[right]添加到当前子数组的元素之和cur_sum中。如果cur_sum大于等于目标值target,说明找到了一个符合要求的子数组。
此时,需要不断移动left指针以缩小子数组的长度,同时更新min_len的值。left指针每次右移,需要将n[left]从cur_sum中减去,并更新left的位置。
当right指针到达数组的末尾时,算法结束,输出min_len作为结果。如果初始的min_len值仍为正无穷,则说明没有找到符合要求的子数组,输出0。
这种方法的时间复杂度是O(n),其中n是数组n的长度。算法只需要遍历一遍数组,通过移动left和right指针,不断更新cur_sum和min_len的值。