算法学习--滑动窗口
目录
滑动窗口
是什么?
核心思想
实际案例分享
长度最小子数组
无重复字符的最长长度
定长子串元音的最大数目
子数组最大平均数I
推荐一篇很牛的文章:讨论 - 力扣(LeetCode)
滑动窗口
是什么?
滑动窗口(Sliding Window)是一种高效的算法技巧,常用于数组或字符串的问题中,用于寻找满足特定条件的子数组或子串。
核心思想
可以简单理解为快慢指针的变种。
- 使用两个指针(通常是 left 和 right )定义一个“窗口”。
- right 指针向右扩展窗口,添加新元素。
- 当窗口不满足条件时, left 指针向右移动,缩小窗口。
- 通过这种方式,避免重复计算,实现O(n)时间复杂度。每次添加或删除元素的时间复杂度都是O(1)
实际案例分享
长度最小子数组
题目来源:209. 长度最小的子数组 - 力扣(LeetCode)
首个题解:
class Solution:def minSubArrayLen(self, target: int, nums: List[int]) -> int:res = float('inf')left = 0for right in range(len(nums)):while sum(nums[left : right + 1]) >= target:res = min(right + 1 - left,res)left += 1res = 0 if res == float('inf') else resreturn res
Solution (O(n²) 时间复杂度) :在 while 循环内每次调用 sum(nums[left : right + 1]) ,这会重新计算整个子数组的和,导致嵌套循环,时间开销巨大。当 n=10^5 时,可能超时。
所以退化成了暴力破解
知识点:内置函数在数组数据很大时会很耗时
正解:
Solution (O(n) 时间复杂度) :使用累积和 s 维护窗口和,每次滑动只加减一个元素,高效。
class Solution:def minSubArrayLen(self, target: int, nums: List[int]) -> int:res = float('inf')s = left = 0for right in range(len(nums)):s += nums[right]while s >= target:res = min(right + 1 - left,res)s -= nums[left]left += 1res = 0 if res == float('inf') else resreturn res
无重复字符的最长长度
题目来源:3. 无重复字符的最长子串 - 力扣(LeetCode)
解:
class Solution:def lengthOfLongestSubstring(self, s: str) -> int:res = 0left = 0no_double = []if len(s) == 1:return 1for right,s_ in enumerate(s):while s_ in no_double:no_double.remove(s[left])left += 1res = max(res,right + 1 - left)no_double.append(s_)return res
思路:
- 利用左右端点动态滑动窗口
- 如果该窗口内不存在重复字符,那么右端点右移(将该字符加入容器)
- 如果该窗口内存在重复字符,那么左端点右移(从容器中删除左端点对应的字符,左端点➕1)
-
注意:字符需要存储在一个容器中,这里我使用的是列表 in 操作判断是否已经存在,如果是那么就是在右端点右移后出现了重复字符
心得:
不是所有程序都可以一边写对,所以在有思路之后应该尝试coding,并不断debug修改直到完成!
优解:
利用计数器哈希表,记录每个字符出现的次数,如果次数大于等于2就是重复了,需要去重并右移端点
class Solution:def lengthOfLongestSubstring(self, s: str) -> int:res = 0left = 0cnt = Counter()for right,s_ in enumerate(s):cnt[s_] += 1while cnt[s_] >= 2:cnt[s[left]] -= 1left += 1res = max(res,right + 1 - left)return res
定长子串元音的最大数目
题目来源:1456. 定长子串中元音的最大数目 - 力扣(LeetCode)
我的分析:
- 滑动窗口定长为k,使用两个指针标记窗口的左右端点,那么两个端点之间的长度应该为k,即right - left + 1 = k,由此可以推导left = right + 1 - k
- 滑动窗口我习惯让右端点遍历滑动,所以第一点公式基于此
- 由于需要记录窗口内的元音个数,当时脑子捋顺不过来了,就使用tmp存储窗口内元音数量,然后每更新一次就添加到ans列表,最后返回max(ans)
- 滑动窗口关键点就是如何移动,如何移除,所以不可能不调试就能一次写完的,除非像灵茶山这样的大佬,要利用好本地的debug工具梳理逻辑,然后修改不断完善并最终实现移动移除操作
我的解:
class Solution:def maxVowels(self, s: str, k: int) -> int:yuan = 'aeiou'left = 0ans = []tmp = 0 for right in range(len(s)):if s[right] in yuan:tmp += 1left = right +1 - kif left < 0:continueans.append(tmp) #这里可以直接 ans = max(ans,tmp)当时脑子抽抽了if s[left] in yuan:tmp -= 1return max(ans)
大佬的解答:
class Solution:def maxVowels(self, s: str, k: int) -> int:ans = vowel = 0for i, c in enumerate(s):if c in "aeiou":vowel += 1if i < k - 1: continueans = max(ans, vowel)if s[i - k + 1] in "aeiou":vowel -= 1return ans
作者:灵茶山艾府
总结:
其实我的代码结构是没问题的,但是在写代码过程中脑子有点小乱,所以没有及时发现max(ans,tmp)方法
子数组最大平均数I
题目来源:643. 子数组最大平均数 I - 力扣(LeetCode)
我的分析:
- 定长滑动窗口,加数----->更新ans----->移除末尾数
- 需要注意的是,整数数组中存在负数,所以ans定义不能是0然后使用max获取,而是要定义为无穷小,即-inf或-float('inf')
我的解:
class Solution:def findMaxAverage(self, nums: List[int], k: int) -> float:ans = -float('inf')left = 0tmp = 0for right in range(len(nums)):tmp += nums[right]left = right + 1 - kif left < 0:continueans = max(ans,tmp/k)tmp -= nums[left]return ans