深入理解滑动窗口算法及其经典应用

Kevin的技术博客.png


在算法设计中,滑动窗口是一种高效的技巧,尤其在处理连续子数组或子串问题时非常有用。滑动窗口的核心思想是使用两个指针,定义一个范围,在这个范围内计算所需的值,然后根据问题的要求移动窗口的边界。本文将详细剖析几道经典题目,结合代码来讲解滑动窗口的原理和应用。

什么是滑动窗口?

滑动窗口技术通常用于解决子数组或子串相关的问题。其主要思想是在数组或字符串上维持一个固定的窗口大小,或在特定条件下调整窗口大小,从而在窗口内进行高效的计算。滑动窗口技术可以帮助我们在O(n)的时间复杂度内解决一些需要遍历整个数组或字符串的问题。
滑动窗口的基本步骤包括:

  1. 初始化窗口的左右边界(通常为两个指针)。
  2. 移动窗口的右边界扩展窗口范围,直至满足某些条件。
  3. 移动窗口的左边界收缩窗口,直至不再满足条件。
  4. 记录或更新需要的结果。

接下来,我们通过几道经典的滑动窗口问题,来深入理解这一技巧的应用。

经典题型分析与讲解

1. 长度最小的子数组

题目描述:
给定一个含有n个正整数的数组和一个正整数
**target**,找出该数组中满足其和大于等于**target**的长度最小的连续子数组,并返回其长度。如果不存在符合条件的子数组,返回0。
滑动窗口思路:

  1. 我们使用两个指针**left****right**表示窗口的左右边界。
  2. 初始时两个指针都指向数组的起点。
  3. 扩展**right**指针,使窗口内的数字和逐渐增大。
  4. 当窗口内的和大于等于**target**时,收缩**left**指针以找到最小的子数组长度。
  5. 在整个过程中,动态更新最小长度。
class Solution {
   
public:
    int minSubArrayLen(int target, vector<int>& nums) {
   
        int left = 0;
        int right = 0;
        int len = INT_MAX;  // 初始化最小子数组长度为无穷大
        int n = nums.size();
        int sum = 0;  // 当前窗口的数字和

        // 遍历数组,扩展右边界
        for (; right < n; right++) {
   
            sum += nums[right];  // 将当前右边界的数字加入窗口的和中

            // 当窗口内的和大于或等于目标值时,缩小窗口
            while (sum >= target) {
   
                len = min(len, right - left + 1);  // 更新最小子数组长度
                sum -= nums[left];  // 减去左边界的数字,将窗口左边界右移
                left++;
            }
        }

        // 如果找不到符合条件的子数组,返回0;否则返回最小长度
        return len == INT_MAX ? 0 : len;
    }
};

复杂度分析:

  • 时间复杂度:O(n),其中n为数组的长度。每个元素在扩展和收缩窗口的过程中最多只会被访问两次。
  • 空间复杂度:O(1),仅使用了常数个额外空间。

2. 无重复字符的最长子串

题目描述:
给定一个字符串
**s**,请你找出其中不含有重复字符的最长子串的长度。
滑动窗口思路:

  1. 使用一个哈希表**hash**来记录窗口内字符的频率。
  2. 移动**right**指针扩展窗口,加入字符到哈希表中。
  3. 如果窗口内出现重复字符,则移动**left**指针收缩窗口,直到不再有重复字符。
  4. 在整个过程中,动态更新最大子串长度。
class Solution {
   
public:
    int lengthOfLongestSubstring(string s) {
   
        int hash[200] = {
    0 };  // 用于记录字符的频率
        int left = 0;
        int right = 0;
        int ret = 0;

        while (right < s.size()) {
   
            hash[s[right]]++;

            // 当出现重复字符时,收缩窗口
            while (hash[s[right]] > 1) {
   
                hash[s[left++]]--;
            }

            // 更新最长子串长度
            ret = max(ret, right - left + 1);
            right++;
        }

        return ret;
    }
};

复杂度分析:

  • 时间复杂度:O(n),字符串的每个字符最多访问两次。
  • 空间复杂度:O(1),哈希表的大小为固定的常数级。

3. 最长重复子数组

题目描述:
给定一个二进制数组
**nums**和一个整数**k**,如果可以将最多**k****0**变成**1**,求最长的连续**1**的长度。
滑动窗口思路:

  1. 使用两个指针**left****right**表示滑动窗口。
  2. 每次扩展**right**指针,将遇到的**0**记录在计数器**counter**中。
  3. 当窗口内**0**的个数大于**k**时,收缩窗口,直到**0**的个数不超过**k**
  4. 在整个过程中,动态更新最大连续**1**的长度。
class Solution {
   
public:
    int longestOnes(vector<int>& nums, int k) {
   
        int left = 0;
        int right 
评论 165
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

DevKevin

你们的点赞收藏是对我最大的鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值