浅谈滑动窗口技巧(力扣题目为例)

技巧解释:

滑动窗口技巧其实是双指针算法合理运用的一种技巧,即通过两个指针的移动,一个指针在前一个在后,动态处理一组数据以便达到简化暴力法的目的,这也说明一般滑动窗口技巧是部分题目暴力法的优化方法。。

图示:

在这里插入图片描述

例题:

例题1:209.长度最小的数组

给定一个含有 n 个正整数的数组和一个正整数 target 。

找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。

在这里插入图片描述
双指针技巧分析:
由于题目要求我们找出和大于target的长度最小子数组,我们可以先定义两个指针指向数组的头部,再定义一个结果变量(result)此处我们初始化为:nums.length + 1,再定义求和变量total;先让j指针移动,total累加求和,当total的值大于等于target时让total减去nums[ i ],每次取result和j-i的最小值,此题目中就是通过j-i来抽象形成一个滑动窗口

代码:

public int minSubArrayLen(int target, int[] nums) {
	//Sliding Window | Two Pointer
	//N is the size of nums
	//Time complexity: O(N)
	//Space complexity: O(1)
	if (nums == null || nums.length == 0) {
		return 0;
	}
	//定义双指针
	int i = 0;
	int j = 0;
	int result = nums.length + 1;
	int total = 0;
	while (j < nums.length) {
		total += nums[j];
		j++;
		//当长度大于等于target时,开始滑动窗口
		while (total >= target) {
		result = Math.min(result,j - i);
		//将i处值“滑出”“窗口”
		total -= nums[i];
		//i++窗口缩小
		i++;
	}
	return result == nums.length + 1 ? 0 : result;
}

总结:对于本题目的“滑动窗口” 算法,最重要的两点就是:1.如何动态维护滑动的窗口;2.如何动态更新题目需求结果!!!对于这两点个人感觉是判断是否能使用“滑动窗口”技巧的关键,也是运用滑动窗口解题的关键,有时我们为了更好的完成以上两点,我们还可以借助一些数据结构的特点实现两点!!!(我们以219。存在重复元素II为例子)

例题2:219.存在重复元素II

给你一个整数数组 nums 和一个整数 k ,判断数组中是否存在两个 不同的索引 i 和 j ,满足 nums[i] == nums[j] 且 abs(i - j) <= k 。如果存在,返回 true ;否则,返回 false 。
在这里插入图片描述
双指针技巧分析:
有了第一个例题总结出来的两个关键待解决点,我们针对那两点分别指出解决方法,1.我们以每个长度为k的子数组为考虑对象(即每次动态维护“滑动窗口”的长度为k)那么本题目该如何动态维护其长度为k呢?我们从数组的基地址开始遍历,当遍历的索引(假设索引为i)大于k时我们通过“i - k - 1”每次移除窗口中的最后一位。2.对于本题目由于只需要判断是否存在,及我们只要判断在长度为k的子数组中,判断是否存在最近的两个符合题目的元素,那么我们可以选用java中HashSet相同后一个元素添加会失败,每次遍历元素时如果不存在则添加到HashSet中,如果添加失败则返回true。(这一点就正如前面一题所总结,我们可以选择合理的数据结构帮助完成元素的比较和相关操作!)

图示:
在这里插入图片描述

代码:

 public boolean containsNearbyDuplicate(int[] nums, int k) {
 	HashSet<Integer> set = new HashSet<>();
 	for (int i = 0; i < nums.lengt; i++) {
 		//动态维护“滑动窗口”
 		if (i > k) {
 			set.remove(nums[i-k-1]);
 		}
 		//如果添加失败
 		//说明在“窗口”中存
 		if (!set.add(i)) {
 			return true;
 		}
 	}
 	return false;
 }

补充:此题目也可以用另一个集合HashMap的特性(相同的后一个元素会覆盖前一个元素)来解答,但不属于“滑动窗口”解法(但也可以看作存在其思想)。

 public boolean containsNearbyDuplicate(int[] nums, int k) {
 	//HashMap
 	//Time Complexiy: O(N)
 	//Space Complexity: O(N)
 	HashMap<Integer, Integer> map = new HashMap<>();
 	for (int i = 0; i < nums.length; i++) {
 		//定义辅助变量,用于对子区间内的元素比较
 		int num = nums[i];
 		//如果map中已经存在值相同元素
 		//并且索引之差小于等于k
 		if (map.containsKey(num) && i - map.get(num) <= k) {
 			return true;
 		}
 		map.put(num,i);
 	}
 	return false;
 }

总结:做完本题目和上一个题目,我们可以发现在施行维护“滑动窗口”之前,我们其实都是先完成了一个先生成一个符合题意的“窗口”的抽象操作,对于209题,我们利用变量“total”达到大于等于“target”时记录下来的下标j和初始另一个下标i形成窗口;对于219题我们先遍历到长度为k的子数组,其后在对长度k维护!!!(接下来的一题对于该操作体现的更加明显)

例题3:643子数组最大平均数I

给你一个由 n 个元素组成的整数数组 nums 和一个整数 k 。

请你找出平均数最大且 长度为 k 的连续子数组,并输出该最大平均数。

任何误差小于 10-5 的答案都将被视为正确答案。
在这里插入图片描述
双指针技巧分析:
题目需要我们找出找出平均数最大且 长度为 k 的连续子数组,那么我们只需要找出和最大,长度为k的子数组即可,为了使用双指针技巧,以及比较元素得出最大和长度为k的 子数组,我们进行如下操作:1.先将遍历到下标为k-1的位置得到“定长窗口”;2.再从下标为k的位置开始遍历,实现j - k完成对“窗口”的维护,并每次比较子数组中的元素和,得出最大和!!!

代码:

public double findMaxAverage(int[] nums, int k) {
	int sum = 0;
	//先遍历到下标为k-1的位置
	//生成“窗口”
	for (int i = 0; i < k; i++) {
		sum += num[i];
	}
	//定义最大结果边练
	//用于每轮维护的比较
	int maxSum = sum;
	//再从k位置开始遍历
	//每次实现维护窗口为定长
	for (int j = k; j < nums.length; j++) {
		sum = sum + nums[j] - nums[j-k];
		maxSum = Math.max(sum,Maxsum);
	}
	//返回结果
	//注意要返回double类型
	return 1.0 * maxSum / k;
}

总结:此题目就比较明显的体现出,先生成窗口的操作。

最后总结:

对于“滑动窗口”技巧我们该明白如下几点:
1.其为部分题解暴力法的优化方法
2.实际上是双指针算法的合理运用
2.用该技巧解题关键在于如何先生成窗口如何依据题意维护窗口如何对窗口内元素进行操作
(个人认为,此处虽然只有三个题目为例(不排除特殊情况)还不能够将滑动窗口的通解逻辑思想体现出来,但是从逻辑的角度看若要维护窗口,那就必须先存在窗口,对于具体问题,还需要具体分析!!!)

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值