本文参考:
题单:分享丨【题单】滑动窗口(定长/不定长/多指针) - 力扣(LeetCode)
本文主要通过Leetcode平台和上面的题单来进一步提炼出一些有意思的,值得反复练习的题目
可以反复尝试和练习,不论是面试还是打算法竞赛,还是单纯的爱好,都有或多或少的帮助
滑动窗口算法原理详解文章可以看我的文章:
不定长滑动窗口算法详细解释(带例题的详细解法)-CSDN博客
本文讲解的题目(建议先尝试一下再看题解):
(题目后面的数字表示难度分,可以直接点击链接跳转到题目)
- 2090. 半径为 k 的子数组平均值 1358
- 904. 水果成篮 1516
- 2024. 考试的最大困扰度 1643
觉得文章写的好可以点个赞,下个星期争取更新中等题和困难题的题解,还有二分查找算法
最近codeforces比赛也挺多的,会有点忙
题一:
题目大意和思路:
两个字符串,判断s2是否包含s1的排列
也就是说字符串s2中是否存在一段连续子字符串满足:
长度与s1相等,且所含有字母跟s1一样
可以维护一个长度等于s1的窗口来遍历s2
先统计开始窗口的字符
窗口开始滑动,维护出来的字符和进入的字符
统计字符数量可以用哈希表来处理
注意:如果使用哈希表处理,那么当窗口内某元素个数为0时候,要删除这个键
哈希表做法(Python):
class Solution:
def checkInclusion(self, s1: str, s: str) -> bool:
k, n = len(s1), len(s)
if k > n:
return False
map1 = defaultdict(int)
map2 = defaultdict(int)
for c in s1:
map1[c] += 1
for i in range(n):
map2[s[i]] += 1
if i >= k:
map2[s[i - k]] -= 1
#如果数量等于0,则删除这个键
if map2[s[i - k]] == 0:
del map2[s[i - k]]
if map1 == map2:
return True
return False
优化:
题目条件明确字符串中只含有小写字母,那么可以用一个长度为26的数组进行计数
可以把空间复杂度降低到常数
优化代码(Python):
class Solution:
def checkInclusion(self, s1: str, s: str) -> bool:
k, n = len(s1), len(s)
if k > n:
return False
cnt1 = [0] * 26
cnt2 = [0] * 26
for c in s1:
cnt1[ord(c) - ord('a')] += 1
for i in range(n):
cnt2[ord(s[i]) - ord('a')] += 1
if i >= k:
c = s[i - k]
cnt2[ord(c) - ord('a')] -= 1
if cnt1 == cnt2:
return True
return False
更多语言(C++,Java,Go)描述:
题二:
2090. 半径为 k 的子数组平均值 - 力扣(LeetCode)
题目大意和思路:
题目中定义了一个半径k的子数组
可以得到这个子数组的长度为 2 * k + 1
然后题目又定义了一个平均值
就是以一个下标为i的半径为k的子数组中所有数字的平均值
很明显,下标 i 前或后不足 k 个元素,此时平均值 是 -1
然后题目要求就是:
返回一个长度为 n 的数组 avgs ,其中 avgs[i] 是以下标 i 为中心的子数组的
半径为 k 的子数组平均值
(题目中说的截断式 整数除法,意思就是直接用/即可,python 用 //)
嗯...应该很好理解
很明显这是一道长度为2 * k + 1的滑动窗口题目,计算滑动窗口的平均值即可
首先,数组前k个和后k个肯定是-1
当这个半径为k的子数组大于原来数组长度n的时候,答案就全是-1
然后只需要计算下标从k 到 n - k - 1这个区间内的值
先求出第一个窗口的和win_sum,也就是从0 到 2 * k + 1
然后滑动窗口,win_sum 减去离开的值,加上进入的值
更新答案即可
代码(Python):
class Solution:
def getAverages(self, nums: List[int], k: int) -> List[int]:
n = len(nums)
res = [-1] * n
win_size = 2 * k + 1
if win_size > n:
return res
win_sum = sum(nums[:win_size])
res[k] = win_sum // win_size
for i in range(k + 1, n - k):
win_sum += nums[i + k] - nums[i - k - 1]
res[i] = win_sum // win_size
return res
更多语言(C++,Java,Go)描述:
题三:
题目大意和思路:
现在有一排树,用一个数组表示,数组上的每一个值表示树的种类,不同的值表示不同的种类
现在有两个篮子,每个篮子只能放特定的水果
从前走到后,问最多能有摘多少棵树的水果
然后题目加了一个限定:
一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。
也就是说你必须在连续的一段区间内进行采摘,且只有两个篮子,那就只能采摘两个类型的水果
根据题目意思,就是在数组上找一个区间的长度最大值,满足这个区间只有两种不同的数字
就是不定长滑动窗口求区间长度最大值
可以使用哈希表来统计窗口内元素的个数
用两个指针维护窗口的左右端点,右指针移动,元素进入窗口
更新哈希表
当窗口内元素不满足要求的时候
就移动左指针,减小窗口大小,并更新哈希表
然后更新在这个过程中窗口的最大值即可
代码(Python):
class Solution:
def totalFruit(self, fruits: List[int]) -> int:
res, l = 0, 0
cnt = defaultdict(int)
for r in range(len(fruits)):
cnt[fruits[r]] += 1
while len(cnt) > 2:
cnt[fruits[l]] -= 1
if cnt[fruits[l]] == 0:
del cnt[fruits[l]]
l += 1
res = max(res, r - l + 1)
return res
更多语言(C++,Java,Go)描述:
题四:
题目大意和思路:
有一个字符串,只含有T和F
现在给一个k,表示操作次数,你可以进行以下操作k次:
把一个字符变成T或者F
现在要求一段连续的,最长的,只含有一个字符的,子字符串长度
题目所要找的连续子字符串可以理解成k个T或者F加上尽可能多的F或者T
也就是说一个连续的区间内,这个区间满足最多k个字符T(或者F)和尽可能多的F(或者T)
那么就是不定长滑动窗口求区间长度最大值
这里有两种情况,第一种是最多k个F,第二种是最多k个T
那么可以遍历两遍字符串
使用哈希表来维护窗口内的字符串数量
定义一个左端点,滑动窗口,元素进入窗口,更新哈希表
当窗口内元素不满足要求的时候
就移动左指针,减小窗口大小,并更新哈希表
然后更新在这个过程中窗口的最大值即可
求出两种情况的最大值,输出这两个的最大值即可
代码(Python):
class Solution:
def maxConsecutiveAnswers(self, answerKey: str, k: int) -> int:
left = 0
x1, x2 = 0, 0
cnt = defaultdict(int)
# 计算最多连续"F"的最长子序列
for right, c in enumerate(answerKey):
cnt[c] += 1
while cnt['F'] > k:
cnt[answerKey[left]] -= 1
if cnt[answerKey[left]] == 0:
del cnt[answerKey[left]]
left += 1
x1 = max(x1, right - left + 1)
# 重置left和cnt以计算最多连续"T"的最长子序列
left = 0
cnt.clear()
for right, c in enumerate(answerKey):
cnt[c] += 1
while cnt['T'] > k:
cnt[answerKey[left]] -= 1
if cnt[answerKey[left]] == 0:
del cnt[answerKey[left]]
left += 1
x2 = max(x2, right - left + 1)
return max(x1, x2)
可以把这个写成一个方法进行优化:
优化代码(Python):
class Solution:
def maxConsecutiveAnswers(self, answerKey: str, k: int) -> int:
def function(c):
left = 0
cnt = defaultdict(int)
res = 0
for right in range(len(answerKey)):
cnt[answerKey[right]] += 1
while cnt[c] > k:
cnt[answerKey[left]] -= 1
left += 1
res = max(res, right - left + 1)
return res
return max(function("T"), function("F"))