滑动窗口
滑动窗口(Sliding Window)是一种常见的算法技巧,用于处理数组、字符串或序列等数据结构的子序列问题。它通过维护一个固定大小的窗口来追踪特定条件下的子序列,以解决各种问题。以下是关于滑动窗口问题的一些总结和归纳:
滑动窗口的基本思想:
- 定义两个指针,通常为窗口的左边界(
left)和右边界(right)。 - 通过移动指针来调整窗口的大小,以满足问题的特定要求。
- 在每个步骤中,更新窗口内的状态,计算所需的结果,并在需要时更新最终答案。
滑动窗口问题的步骤:
- 初始化指针和其他变量,定义窗口大小。
- 开始遍历数组或序列,根据问题要求移动窗口的边界。
- 在窗口内计算所需的结果,更新最终答案。
- 根据问题要求,调整窗口的大小和边界。
- 重复步骤2-4,直到遍历完整个数组或序列。
常见问题类型:
- 最大/最小值问题:找到滑动窗口内的最大或最小值。
- 子数组和子串问题:找到满足特定条件的子数组或子串。
- 字符串匹配问题:在字符串中找到满足特定条件的子串或字符。
- 计数问题:统计滑动窗口内满足条件的元素个数。
- 移动问题:通过移动窗口解决问题,如平均值、中位数等。
滑动窗口的优点:
- 时间复杂度通常为 O(N),其中 N 是数组或序列的长度,因此在大多数情况下,滑动窗口是一个高效的解决方案。
- 在处理连续子序列问题时,滑动窗口通常比传统的迭代方法更快,因为它避免了重复计算。
滑动窗口的注意事项:
- 窗口大小通常是固定的,但在某些问题中可能会根据条件变化。
- 窗口内的元素可以是连续的,也可以是不连续的,具体取决于问题的性质。
- 在移动窗口边界时,要注意边界条件,以避免数组越界。
- 对于某些问题,可能需要使用额外的数据结构(如哈希表)来辅助计算。
滑动窗口相关的题目
题目描述:给你一个由 n 个元素组成的整数数组 nums 和一个整数 k 。
请你找出平均数最大且 长度为 k 的连续子数组,并输出该最大平均数。
任何误差小于 10-5 的答案都将被视为正确答案。
/**
* 在数组中求连续n个元素的最大平均数(最大和)
* 1 求数组前n-1个元素的和
* 2 从n开始遍历数组,
* 加上当前的元素并且减去尾部的元素即为当前和
* 与之前的最大和比较,取最大值
* 最大和除以n即为最大平均数
*
* @param nums 输入数组
* @param k k个元素
* @return 长度为n的最大平均数
*/
public double findMaxAverage(int[] nums, int k) {
int tmp = 0;
for (int i = 0; i < k; i++) {
tmp+=nums[i];
}
int max = tmp;
for (int i = k; i < nums.length; i++) {
tmp = tmp + nums[i] - nums[i-k];
max = Math.max(max,tmp);
}
return max / (k * 1.0);
}
题目描述:给定一个二进制数组 nums 和一个整数 k,如果可以翻转最多 k 个 0 ,则返回 数组中连续 1 的最大个数 。
/**
* 反转n个值为0的元素,返回数组中连续1的最大个数:
* 遍历数组:
* 使用右指针找到第n+1个0的位置
* 滑动窗口,移动左指针:
* 数组尾部的值为0,则0的数量要减1,右移左指针
* 数组尾部的值为1,则0的数量不变,右移左指针
* 此时,左右指针之间的距离即为本次的值为1的长度,与之前的最大长度作比较,取较大的那个
*
* @param nums 输入数组
* @param k 翻转为1的元素数量
* @return 连续为1的最大个数
*/
public int longestOnes(int[] nums, int k) {
int leftIndex = 0;
int maxLen = 0;
int zeroCount = 0;
for(int i = 0; i < nums.length; ++i){
if(nums[i] == 0){
zeroCount++;
}
while(zeroCount > k){
if(nums[leftIndex] ==0){
zeroCount--;
}
leftIndex++;
}
maxLen = Math.max(maxLen,i-leftIndex + 1);
}
return maxLen;
}
题目描述:给你一个二进制数组 nums ,你需要从中删掉一个元素。
请你在删掉元素的结果数组中,返回最长的且只包含 1 的非空子数组的长度。
如果不存在这样的子数组,请返回 0 。
/**
* 删除1个元素后,输出数组中连续为1的最大长度:
* 1 初始化两个指针,left 和 right,表示子数组的左右边界,以及一个变量 zero_count 记录当前子数组内0的个数。
* 2 遍历数组,对于每个元素执行以下步骤:
* 如果当前元素为0,增加 zero_count 计数。
* 如果 zero_count 大于1(即子数组内有超过一个0),移动 left 指针,直到 zero_count 变为1,以保持子数组内最多只有一个0。
* 计算当前子数组的长度(即 right - left),并与之前的最大长度比较,更新最大长度。
* 将 right 指针向右移动,继续遍历下一个元素。
* 3 最终返回最大长度。
* @param nums 输入数组
* @return 输出仅包含1的数组的最大长度
*/
public int longestSubarray(int[] nums) {
int max = 0;
int left=0;
int zero_count = 0;
for(int i = 0; i < nums.length; ++i){
if(nums[i] == 0){
zero_count++;
}
while(zero_count > 1){
if(nums[left] == 0){
zero_count--;
}
left++;
}
max= Math.max(max, i - left );
}
return max;
}
小结:
滑动窗口的时间复杂度通常为 O(N),其中 N 是数组或序列的长度,因此在大多数情况下,滑动窗口是一个高效的解决方案。在处理连续子序列问题时,滑动窗口通常比传统的迭代方法更快,因为它避免了重复计算。 总之,滑动窗口是一个强大的工具,适用于解决各种数组、字符串和序列相关的问题。通过理解其基本思想和应用方法,你可以更有效地解决各种编程竞赛、算法竞赛和实际应用中的问题。