学习目标
- 了解滑动窗口的基本原理;
- 学会用使用python语言解答滑动窗口经典题目;
- 了解双指针的基本原理;
- 学会使用python语言解答双指针经典题目;
滑动窗口
209. 长度最小的子数组
https://leetcode.cn/problems/minimum-size-subarray-sum/description/
暴力解法
-
目标是找子数组,暴力遍历所有的子数组
-
枚举子数组的下标i,对于每个开始下标i:
- 枚举子数组的结束下标j(i<=j)
- 使得nums[i]到nums[j]的元素和大于等于s
- 更新子数组的最小长度(j-i+1)
-
优化点:
- 前提:nums数组中全是正整数
- 枚举子数组的结束下标时,如果nums[i]到nums[j]的元素和大于等于s时,中断对结束下标的枚举
class Solution:def minSubArrayLen(self, target: int, nums: List[int]) -> int:if not nums:return 0n = len(nums)ans = n + 1# 枚举子数组的开始下标i;for i in range(n):total = 0# 枚举子数组的结束下标jfor j in range(i, n):total += nums[j]# 更新子数组的最小长度(j-i+1)if total >= target:ans = min(ans, j - i + 1)breakif ans == n + 1:return 0else:return ans
滑动窗口
- 定义两个指针start和end分别表示子数组(滑动窗口)的开始位置和结束位置,初始状态下都指向下标0;
- 维护变量total存储子数组中的元素和(即从nums[start]到nums[end]的元素和)
- 迭代:
- 滑动窗口终止位置移动:在每一轮迭代的最后,将end右移,将nums[end]加到total
- 滑动窗口起始位置移动:若total>=s,则更新子数组最小长度end-start+1,然后将nums[start]从total中减去并将start右移,直到total<s,同时不断更新子数组的最小长度
class Solution:def minSubArrayLen(self, target: int, nums: List[int]) -> int:if not nums:return 0n = len(nums)ans = n + 1# 滑动窗口的开始位置和结束位置start, end = 0, 0total = 0 while end < n:total += nums[end]while total >= target:ans = min(ans, end - start + 1)total -= nums[start]# 滑动窗口起始位置移动start += 1# 滑动窗口终止位置移动end += 1if ans == n + 1:return 0else:return ans
滑动窗口图解
- 扩张窗口:为了找到一个可行解,找到了就不再扩张,因为扩张不再有意义;
- 收缩窗口:在长度上优化该可行解,直到条件被破坏;
- 继续寻找下一个可行解,然后再优化到不能优化
567. 字符串的排列
https://leetcode.cn/problems/permutation-in-string/description/
题目建模:
- 无需全排列:排列不会改变字符串中每个字符的个数,所以只有两个字符串每个字符的个数均相等时,一个字符串才是另一个字符串的排列
- 建模:s1的每个字符出现的次数,与s2某个子串的每种字符出现次数相同。
滑动窗口解法
- 维护两个数组dic1和dic2,其中dic1用于统计s1中各个字符的个数,dic2用于统计当前遍历子串中的各个字符的个数;
- 由于需要遍历的子串长度均为m1,我们可以使用一个固定长度为m1的滑动窗口来维护dic2;
- 滑动窗口每向右滑动一次,就多统计一次进入窗口的字符,少统计一次离开窗口的字符。然后,判断dic1是否与dic2相等,若相等则意味着当前窗口内的子串是s1的排列之一。
class Solution:def checkInclusion(self, s1: str, s2: str) -> bool:m1 = len(s1)m2 = len(s2)if m1 > m2:return False# 采用有序字典来比较,由于只包含小写字母,采用数组来模拟有序字典dic1 = [0] * 26dic2 = [0] * 26# 滑动窗口长度固定为m1for i in range(m1):dic1[ord(s1[i]) - ord('a')] += 1dic2[ord(s2[i]) - ord('a')] += 1if dic1 == dic2:return True# 滑动窗口每向右滑动一次,就多统计一次进入窗口的字符,少统计一次离开窗口的字符for i in range(m1, m2):dic2[ord(s2[i-m1]) - ord('a')] -= 1dic2[ord(s2[i]) - ord('a')] += 1if dic1 == dic2:return Truereturn False
双指针
- 滑动窗口有本质上是双指针,窗口的开始位置和结束位置便是两个指针,窗口的滑动对应指针的移动;
- 双指针有时也叫快慢指针,在数组里是用两个整型代表下标,在链表里面是两个指针,一般能将O(N^2)的时间复杂度降为O(N);
- 两个指针的位置一般在第一个元素和第二个元素或者第一个元素和最后一个元素,快指针在前“探路”,当符合某种条件快慢指针向前挪动。
- 同向双指针,在同向双指针套路中,两个指针朝相同方向移动,但是快慢不同。
- 反向双指针, 反向双指针中的两个指针反向而行。
11. 盛最多水的容器
https://leetcode.cn/problems/container-with-most-water/description/
双指针解法
- 指针每一次移动,都意味着排除掉一根柱子;
- 如何确定排除哪一根柱子。
class Solution:def maxArea(self, height: List[int]) -> int:#双指针"""1、设置两个指针,一个指向起始位置,一个指向末尾,则s = min(h[i],h[j])*(j-i);2、指针向内移动,移动高板,则面积肯定减少;3、移动矮板,面积可能增加4、直至指针相遇,返回面积最大值"""#coding i, j = 0, len(height)-1area = 0ans = 0while i < j:area = min(height[i],height[j]) * (j-i)ans = max(area,ans)if height[i] < height[j]:i += 1else:j -= 1return ans