简介
所谓滑动窗口法,又称为“寸取法”,一般用来解决查找满足依一定条件的连续区间的特殊性质(长度等) 等一类问题。
由于区间是连续的,因此当整个区间发生变化时,可以通过对旧有的计算结果对搜索空间的剪枝(一般就是滑动窗口最左侧滑出的部分),从而减少了重复计算、降低了时间复杂度、避免了暴力的搜索。
往往类似于“请找到满足xx条件的最x区间/子串/子数组”这一类问题都可以使用滑动窗口法来进行解决。
LeetCode滑动窗口问题专区
涉及题目
中等难度
LeetCode 3. 无重复字符的最长子串
题目地址3. 无重复字符的最长子串
题目描述
给定一个字符串,请你找出其中不含有重复字符的最长子串的长度。
示例 1:
输入: s = “abcabcbb”
输出: 3
解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。
示例 2:
输入: s = “bbbbb”
输出: 1
解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。
示例 3:
输入: s = “pwwkew”
输出: 3
解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。
请注意,你的答案必须是 子串 的长度,“pwke” 是一个子序列,不是子串。
示例 4:
输入: s = “”
输出: 0
提示:
- 0 <= s.length <= 5 * 104
- s 由英文字母、数字、符号和空格组成
题目思路
对于这道题,需要找到最长的连续、不重复字符的子串。
经过之前的经验,我们可以得知,可以使用滑动窗口的方法来找到这样的满足条件的连续字符串。
这里,条件是对应的子串中不能有重复的字符。因此,这里我们需要定义一个hash_map来保存最新的字符所对应的下标,并在right遍历字符串的过程中,定义left,表示左边界——即不存在重复字符的最左位置。
对于遍历到的字符,判断该字符是否在对应的字符字典中,以及如果在其中,判断其出现的位置是否在left的范围内:
- 如果该字符未曾出现过,或者出现的位置在left的左边,说明该字符是可以作为不含重复字符串的子串的,并计算最新的最长子串长度。
- 否则,就需要更新left——注意这里需要跳过当前的字符,因此left = right + 1。
- 每次,都需要更新字符出现的位置字典。
最终,在right遍历到最后一个字符后,就可以得到这样最长的结果了。
题目解法
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
letter_index_map = {}
left, right = 0, 0
res = 0
while right < len(s):
curr_letter_index = letter_index_map.get(s[right])
if curr_letter_index is None or curr_letter_index < left:
res = max(res, right - left + 1)
else:
left = curr_letter_index + 1
letter_index_map[s[right]] = right
right += 1
return res
LeetCode 209. 长度最小的子数组
题目地址
209. 长度最小的子数组
题目描述
给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
示例 1:
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
示例 2:
输入:target = 4, nums = [1,4,4]
输出:1
示例 3:
输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0
提示:
- 1 <= target <= 109
- 1 <= nums.length <= 105
- 1 <= nums[i] <= 105
题目思路
对于这道题,首先需要注意的是,并不是说要求连续子序列的累加和等于target,而是要大于target。
如果使用暴力求解的办法,自然也是可以做到的。但是,从题目的需求中我们可以看出,整体要求是满足“滑动窗口”的情景的。
这里,整体的算法流程接口也类似,首先,外层有一个right指针,内层再做相关的数据判断和左侧边界的处理——由于这种处理是基于条件的,因此也是需要一个while循环来实现。
因此,在算法的实现上,我们还是按照外层遍历、内层条件判断的策略,遍历整个数组,获得最终的结果。
题目解法
class Solution:
def minSubArrayLen(self, target: int, nums: List[int]) -> int:
left, right = 0, 0
total_sum, res = 0, float('inf')
while right < len(nums):
total_sum += nums[right]
while total_sum >= target:
res = min(res,right-left+1)
total_sum -= nums[left]
left += 1
right += 1
return 0 if res == float('inf') else res
LeetCode 1004. 最大连续1的个数 III
题目描述
给定一个由若干 0 和 1 组成的数组 A,我们最多可以将 K 个值从 0 变成 1 。
返回仅包含 1 的最长(连续)子数组的长度。
示例 1:
输入:A = [1,1,1,0,0,0,1,1,1,1,0], K = 2
输出:6
解释:
[1,1,1,0,0,1,1,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 6。
示例 2:
输入:A = [0,0,1,1,0,0,1,1,1,0,1,1,0,0,0,1,1,1,1], K = 3
输出:10
解释:
[0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 10。
提示:
- 1 <= A.length <= 20000
- 0 <= K <= A.length
- A[i] 为 0 或 1
题目思路
对于这道题,就是一道较为经典的滑动窗口类问题。
在这道题中,是最初的求最长连续1的长度的变形——中间有0,并且有替换的数量,进而求最长的1的长度。
这里,我们定义一个滑动窗口 [left, right] 后,需要保证的窗口内全部都是1,这样最大的长度就是right - left + 1了。
但是,由于有0,这样就需要right在向右滑动时,需要判断A[right]是否为0:
- 如果是1,好说,直接向右滑动即可;
- 如果是0,则需要记录curr_zero_num +=1,表示这一位我们置为了1——当然, 不可真的直接修改为1,否则后续left就无法得知A[left]原先到底是0还是1了。
当curr_zero_num为K时,则说明所有的0置为1的机会、我们此时已经用完了,因此,下一步就需要left向右移动了:
- 如果A[left]为1,也好说,直接继续右滑即可;
- 如果是0,则说明可用的机会多了一个——原先为0的,目前我们已经不考虑了;
通过left向右的滑动,知道curr_zero_num再次小于K即可。
最后,不断计算right - left + 1的最大值即可。
题目解法
class Solution:
def longestOnes(self, A: List[int], K: int) -> int:
res = 0
n = len(A)
left = 0
right = 0
curr_zero_num = 0
while right < n:
if A[right] == 0:
curr_zero_num += 1
while curr_zero_num > K:
if A[left] == 0:
curr_zero_num -= 1
left += 1
res = max(res, right - left + 1)
right += 1
return res