在一开始接触滑动窗口时,可能会觉得无从下手,但是相关题目做多了以后,就可以整理出一套框架,搞清楚套路后滑动窗口问题也并不是特别的难。
需要注意的是,滑动窗口包括定长和不定长窗口,在一些细节上还是有区别的。
文章目录
- 一、643. 子数组最大平均数 I
- 题解
- 代码
- 二、3. 无重复字符的最长子串
- 题解
- 代码
- 三、209. 长度最小的子数组
- 题解
- 代码
- 四、1456. 定长子串中元音的最大数目
- 题解
- 代码
- 五、1695. 删除子数组的最大得分
- 题解
- 代码
- 六、438. 找到字符串中所有字母异位词
- 题解
- 代码
- 七、567. 字符串的排列
- 题解
- 代码
- 八、1004. 最大连续1的个数 III
- 题解
- 代码
- 九、1052. 爱生气的书店老板
- 题解
- 代码
- 十、1423. 可获得的最大点数
- 题解
- 代码
- 总结
一、643. 子数组最大平均数 I
题解
这个题目本质上是一个滑动定窗口问题,定义两个指针left,right 分别是窗口的左右边界。当right-left+1==k时,窗口形成,若大于k则左边界向前走。只有窗口形成时才进行求平均值的操作。
代码
class Solution:
def findMaxAverage(self, nums: List[int], k: int) -> float:
if not nums or not k:
return 0
sum = 0
max_average = -float('inf')
left,right = 0,0
# 创建窗口
while right < len(nums):
sum += nums[right]
# 判断窗口是否创建完毕,如果窗口已形成,就计算平均值
if right-left+1 == k:
average = sum/k
max_average = max(max_average,average)
# 当超过窗口的范围时,left指针就缩小范围
if right-left+1 >= k:
sum -= nums[left]
left += 1
right += 1
return max_average
二、3. 无重复字符的最长子串
题解
滑动窗口问题可以当做队列来看,队列的特性就是先进先出
当窗口已满或者不符合要求时,就应该出队,也就是操作原序列的左边。
代码
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
if not s:
return 0
max_count = 0
que = []
# 遍历整个序列
for right in range(len(s)):
# 如果已在队列里,就进行出队操作
while s[right] in que and que:
que.pop(0)
# 否则进队
que.append(s[right])
# 返回最大的长度
max_count = max(max_count,len(que))
return max_count
三、209. 长度最小的子数组
题解
题目中说的很明确,连续子数组,这不就是一个滑动不定窗口吗?对于滑动不定窗口,先移动right,直到不符合条件,再移动left
代码
def minSubArrayLen(nums,target):
if not target and not nums:
return 0
sum = 0
res = float('inf')
left,right = 0,0 # 窗口的左右边界
while right < len(nums):
sum += nums[right]
while sum >= target:
subLength = right-left+1 # 窗口长度
res = min(subLength,res)
sum -= nums[left]
left += 1
right += 1
return res
四、1456. 定长子串中元音的最大数目
题解
滑动定窗口,还要新建一个判断是否是元音字母的函数
代码
'''
滑动窗口问题,算是比较简单的题目
'''
class Solution:
def maxVowels(self, s: str, k: int) -> int:
def isYuan(s):
if s=='a' or s=='e' or s=='i'or s=='o'or s=='u':
return 1
else:
return 0
count = 0
max_num = 0
if not s or not k:
return 0
right = 0
while right<len(s):
# 依次向后遍历,判断是否是元音字母
count += isYuan(s[right])
right += 1
# 窗口已经形成,如果要再判断,必须从窗口前面去除
if right>=k:
# 对符合要求的最大长度进行更新
max_num = max(max_num,count)
# 窗口大小是固定的,必须先取出一个,才可以进行插入
count -= isYuan(s[right-k])
return max_num
代码二,感觉这个比较好理解,更形象一点
'''
滑动窗口问题,算是比较简单的题目
'''
class Solution:
def maxVowels(self, s: str, k: int) -> int:
def isYuan(s):
if s=='a' or s=='e' or s=='i'or s=='o'or s=='u':
return 1
else:
return 0
count = 0 # 窗口中元音字母的个数
max_count = 0 # 最多的元音字母的个数
left,right = 0,0
while right <len(s):
count += isYuan(s[right])
if right-left+1 == k:
max_count = max(max_count,count)
right += 1
if right-left+1 > k:
count -= isYuan(s[left])
left += 1
return max_count
五、1695. 删除子数组的最大得分
看来我真得好好学学语文,这个题目愣是没看懂是啥意思,看了评论区才明白。
给你一个正整数数组 nums ,求累加和最大的无重复元素的连续子数组,返回其累加和的值。
题解
使用队列来解决这个问题,只要遍历到一个新的元素,这个元素一定会入队,并且如果队中已有这个元素,则一直出队,直到队中无重复元素。
代码
'''给你一个正整数数组 nums ,求累加和最大的无重复元素的连续子数组,返回其累加和的值。'''
class Solution:
def maximumUniqueSubarray(self, nums: List[int]) -> int:
res = 0
que = collections.deque()
maxN = 0
for i in range(len(nums)):
maxN += nums[i]
# 如果队列中有这个数,则将之前面的全部出队
while nums[i] in que:
temp = que.popleft()
maxN -= temp
que.append(nums[i])
res = max(maxN, res)
return res
六、438. 找到字符串中所有字母异位词
题解
这个题目我个人认为是在滑动窗口问题中比较难的,这是一个滑动定窗口,窗口的大小就是p的长度。
因为题目中已经说了,全都是小写字母,所以可以新建一个长度为26的数组,这个数组用来存储哪个字母出现过以及出现了几次。当然,数组中的值,是元素相对于字母a的相对位置。这个相对位置可以通过ord()来实现。
如果不理解while循环,一定要自己debug一下或者手写实现。
代码
def findAnagrams_re(s,p):
m,n = len(s),len(p)
if n>m:
return None
s_cnt = [0]*26
p_cnt = [0]*26
res = []
# 对p_cnt进行初始化
for i in range(n):
p_cnt[ord(p[i])-ord('a')] += 1
left = 0
# 遍历s数组
for right in range(m):
cur_right = ord(s[right])-ord('a')
s_cnt[cur_right] += 1
# 如果这个字母在p中未出现过,或者在s中出现的次数大于在p中出现的次数,移动左窗口
while s_cnt[cur_right]>p_cnt[cur_right]:
cur_left = ord(s[left])-ord('a')
s_cnt[cur_left] -= 1
left += 1
# 如果窗口形成,则把左边界加入到res中
if right-left+1 == n:
res.append(left)
return res
七、567. 字符串的排列
这个题目和上面那个题目简直是一模一样。
题解
就不多说啦,如果有符合条件的窗口,就直接返回True就好了。
代码
class Solution:
def checkInclusion(self, s1: str, s2: str) -> bool:
if not s1 or not s2:
return False
m,n = len(s1),len(s2)
if n<m:
return False
s1_cnt = [0]*26
s2_cnt = [0]*26
for i in range(m):
s1_cnt[ord(s1[i])-ord('a')] += 1
left = 0
for right in range(n):
cur_right = ord(s2[right])-ord('a')
s2_cnt[cur_right] += 1
while s2_cnt[cur_right]>s1_cnt[cur_right]:
cur_left = ord(s2[left])-ord('a')
s2_cnt[cur_left] -= 1
left += 1
if right-left+1 == m:
return True
return False
八、1004. 最大连续1的个数 III
这个题目真的很好,个人觉得很有意思。其实就是一开始想不出思路,看了别人的思路觉得恍然大悟。
题解
还是滑动窗口的老方法,既然是窗口了,你的左右边界得有吧,即left,right。其他参数用到什么定义什么。定义zeros记录窗口中0的个数,当zeros>k时,窗口左边界移动。
代码
class Solution:
def longestOnes(self, nums: List[int], k: int) -> int:
if not nums:
return None
left = 0
max_count = 0
zeros = 0
# 遍历数组
for right in range(len(nums)):
if nums[right]==0:
zeros += 1
# 移动左边界
while zeros>k:
if nums[left]==0:
zeros -= 1
left += 1
# 返回结果
max_count = max(max_count,right-left+1)
return max_count
九、1052. 爱生气的书店老板
题目描述是有问题的,customers[i] 是在第 i 分钟开始时进入商店的顾客的数量,并非编号。可以查看英文描述。
题解
第一种解法
1.我们可以先将原本就满意的客户加入答案,同时将对应的 customers[i]置为0。
2.之后的问题转化为:在 customers中找到连续一段长度为 minutes的子数组,使得其总和最大。
当时看到这个解法,真的太牛了,看来自己在算法路上还是道阻且长
第二种解法
这个解法就是滑动窗口比较通俗的解法,估计看了上面的题目,这个应该也会看得明白。就是多加了if语句判断。
代码
class Solution:
def maxSatisfied(self, customers: List[int], grumpy: List[int], minutes: int) -> int:
sum_ = 0
for i in range(len(customers)):
if grumpy[i] == 0:
sum_ += customers[i]
customers[i] = 0 # 一定记得置为0
max_,cur = 0 ,0
for i in range(len(customers)):
cur += customers[i]
if i >= minutes:
cur -= customers[i-minutes]
max_ = max(cur,max_)
return sum_ + max_
class Solution:
def maxSatisfied(self, customers: List[int], grumpy: List[int], minutes: int) -> int:
# 所有不生气的时间内的顾客总数
sum_ = 0
for i in range(len(customers)):
if grumpy[i] == 0:
sum_ += customers[i]
# 生气的时间区间内,会让多少顾客不满意
cur_value = 0
# 先计算起始区间
for i in range(minutes):
if grumpy[i] == 1:
cur_value += customers[i]
# 不满意顾客的最大值
res_value = cur_value
for i in range(minutes,len(customers)):
if grumpy[i]==1:
cur_value += customers[i]
if grumpy[i-minutes]==1:
cur_value -= customers[i-minutes]
res_value = max(res_value,cur_value)
return sum_ + res_value
十、1423. 可获得的最大点数
有一说一,这个题目的思路我也觉得巨好
题解
题目中让我们求可获得的最大点数,可能一开始很难和滑动窗口问题关联起来,从两边进行操作这怎么用滑动窗口啊。其实这个题目变一下,你肯定立马可以想到解法,让你求一固定长度的窗口内的数值和最小的区间。最后让总和减去这个最小的就可以得到我们想要的答案了。
代码
'''
找到长度为window_size的最小区间,再减去就好了
'''
class Solution:
def maxScore(self, cardPoints: List[int], k: int) -> int:
n = len(cardPoints)
window_size = n-k # 窗口大小
left,right,sum_ = 0,0,0
min_value = float('inf') # 最小的窗口和
window_value = 0 # 窗口值
while right<n:
sum_ += cardPoints[right] # 数组中全部的和
window_value += cardPoints[right]
# 如果超过窗口长度,左边界后移
if right-left+1 > window_size:
window_value -= cardPoints[left]
left += 1
# 进行一系列的操作
if right-left+1 == window_size:
min_value = min(min_value,window_value)
right += 1
return sum_ - min_value
这个题目其实可以当做滑动定窗口题目的模板
# 先遍历数组
# 进行一系列操作(求和等)
# 当超过窗口长度时
# 进行一系列操作
# 左边界右移
# 当等于窗口长度时
# 结合题意求出答案
# 返回结果