算法技巧之滑动窗口

在算法题目中,经常会遇到给定一个数组,找出符合某种特性的连续子数组。这时候可以采用滑动窗口的方法。

例如,给定数组A = [1,1,3,2,1,0,2],找出满足和小于等于5的最长连续子数组

采用暴力的方法,需要用一层循环枚举子数组的起始位置,然后用第二层循环枚举子数组的长度,时间复杂度为O(N^2)。

采用滑动窗口的方式即可将算法复杂度降到O(N)。

其核心思想是,采用两个指针,分别指代子数组的左右边界。找到以每个坐标为起点的比之前长的子数组。以上述问题为例,刚开始,left=0, right = 0, ans =0  window_sum = 0

现在我们想要找到以[1]为起点,并且符合条件的最长子数组。

此时我们移动right,可以发现当right=3的时候,和就已经大于5了,所以以[1]为开始的最长子数组的长度是3,更新ans = 3,window_sum = 5

此时我们将left向右移动,此时可以发现指代的是[1,3,2]这样一个数组,我们可以直接认为以第二个[1]为开始,并不存在最优解。这是因为,如果想要比之前的3还要长,至少要从现在这个2开始,就算之前[1,3]满足条件,这也毫无意义,我们想要找的是比之前长的子数组,所以我们再将left向右移动,直到结束。

可以发现这种算法的魅力所在是left和right一直都在向右移动,所以说复杂度为2*O(N)。其通过不断寻找最长这样一个思想,每次限定了right最小的起始位置,从而舍弃了很多一定不是最优解的计算,达到了O(N)的时间复杂度。

算法如下:

window_sum = 0
left = 0
length = 0
for right in range(len(nums)):
    window_sum += nums[right]
    while window_sum > cost:
        length = max(length, right-left)
        window_sum -= nums[left]
        left += 1
return max(length, right - left + 1)

其中注意一点的是,如果最后的结果包含最后一位数字,也就是最后没有进入while循环来更新length,所以我们需要在外层额外判断这种情况。

有了找到小于等于K的最长连续子数组,我们自然会想到 找到大于等于k的最短连续子数组 这样一个问题。

和上面的思想同理,我们首先找到起始位是0的最短子数组,然后将left向左边滑动,right保持不变。这是因为如果存在一个新的右界

值得注意的是,由于是满足条件进入的while,所以长度是right-left + 1,并且没有外面的额外判断。

代码如下:

window_sum = 0
left = 0
length = float('inf')
for right in range(len(nums)):
    window_sum += nums[right]
    while window_sum >= cost:
        length = min(length, right-left+1)
        window_sum -= nums[left]
        left += 1
return length

 

你可能感兴趣的:(leetcode)