我爱学算法之——滑动窗口攻克子数组和子串难题(上)

发布于:2025-03-19 ⋅ 阅读:(14) ⋅ 点赞:(0)

现在来学习"滑动窗口"这一算法思想。

至于什么是"滑动窗口"呢?简单来说就是同向双指针;现在来通过题目来了解什么是"滑动窗口"

一、长度最小的子数组

题目链接:长度最小的子数组

题目解析

在这里插入图片描述

先来看题目,给了一个数组nums和一个整数target,让我们找到nums的一个满足条件(条件:子数组的和大于target)的最长子数组。

算法思路

首先第一次看到子数组/子串问题的算法题,能想到的思路就肯定是暴力枚举了;现在就先来看暴力枚举如何解决的。

暴力枚举

我们依次枚举整个数组的每一个子数组,并判断是否满足条件;满足条件且长度小于最总结果,就更新最终结果。

上面意思呢?

在这里插入图片描述

暴力解法的大致思路如上述所示;现在来看一下暴力枚举有哪些操作是不必要的

  • right第一次找到满足条件的位置并更新结果后,left++rightleft位置再重新遍历

这个操作感觉有些多余了,因为我们right遍历时第一次遇到满足条件的就停了下来;

如果rightleft++后的位置开始再遍历到上次满足条件位置的位置,这一个遍历过程很多余;

什么意思呢?

就以暴力枚举中的示例来说:

right第一次遍历到下标为3位置恰好满足条件;此时区间是[0,3],我们更新结果之后,left++

right从下标为1的位置开始再次遍历;

我们直到[0,3]区间刚满足条件,那区间[1,1][1,2]是一定不满足条件的[1,3]才有可能满足条件。

那这样我们就可以想办法省略这个不必要的步骤:

找到满足条件的区间,并更新结果并lef++后,如何去解决right重新遍历的问题?

解决问题,我们要找到问题的本质

**暴力枚举**中为什么要重新进行遍历,本质问题就是我们不知道left++后,区间[left,right]的和;(所以暴力枚举才会进行重新遍历)

那我们现在定义一个变量sum来实时记录区间[left,right]的和,这样不就不需要重新遍历了吗。

我们只需要在right向后遍历和left更新时,顺手更新一下sum的值就可以了。

滑动窗口

其实滑动窗口就是优化了暴力枚举解法中不必要的部分

知道了如何去解决暴力枚举中不必要的问题,现在来实现

在这里插入图片描述

通过上图所示推到,我们的想法是可行的,现在来看整体的一个思路

滑动窗口,为什么称为滑动窗口?

就是rightleft在遍历更新的过程中维护了一段区间[left , right],这个区间像窗口一样在数组中滑动。

现在来看实现这个问题的一个整体思路:

  • 进窗口:让right向右遍历,更新区间[left , right]的和sum;称为进窗口(就是让right遍历到的元素进入(窗口)区间内。
  • 出窗口:如果当前区间满足条件(区间大于target),就更新结果,然后让left++,并更新区间[left , right]的和;重复上述操作直到区间不满足条件。
  • 更新结果:至于什么时候更新结果,每一道题都不一样,根据题目中的要求再决定什么时候更新结果。

代码实现

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int n = nums.size();
        int ret = n+1;
        int l=0,r=0;
        int sum =0;
        while(l<n)
        {
            //进窗口
            sum += nums[l++];
            //出窗口
            while(sum>=target)
            {
                int x= l-r;
                if(x < ret)
                    ret = x;
                sum-=nums[r++];
            }
        }
        if(ret == n+1)
            return 0;
        return ret;
    }
};

二、无重复字符的最长子串

题目链接:无重复字符的最长子串

题目解析

在这里插入图片描述

题目要求找出来给定字符串的所有子串中,不包含重复字符的最长子串;(也是子串问题)

  • s字符串中的字符由英文字母、数字、符号和空格组成。

算法思路

这里首先还是来看暴力解法

枚举出来所有不包含重复字符的子串

在其中找到最长的然后返回。

这里先来看一下如何处理重复字符的问题:

这里可以使用一个hash表,其中记录每一个字符出现的次数;

这样使用leftright双指针遍历的时候,遍历到hash[s[right]] > 1就代表当前区间内存在重复字符。

当然这里所有数组来模拟hash就可以了

现在来看使用滑动窗口如何去解决。

进窗口:首先right向右遍历,遍历过程中hash[s[right]]++更新当前字符出现的次数;

出窗口:当hash[s[right]] >1时就表示当前区间不满足条件,就要left++并且hash[s[left]]--更新区间内字符出现的字符;直到hash[s[right]]<=1

更新结果:这里每一次出完窗口就表示找到了一个满足条件的区间,所以出完窗口之后更新结果即可。(这里在right刚开始遍历,每一次出完窗口,不一定会进入出窗口,但是也会更新结果,不会遗漏)。

代码实现

这里写代码时注意,我们数组模拟hash,数组大概开辟128就差不多够了。

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int hash[128]={0};
        int n = s.size();
        int left = 0, right = 0;
        int ret = 0;
        while(right < n)
        {
            //进窗口
            hash[s[right]]++;
            while(hash[s[right]]>1)
                hash[s[left++]]--;
            ret = max(ret,right-left+1);
            right++;
        }
        return ret;
    }
};

三、最大连续1的个数 III

题目链接:最大连续1的个数 III

题目解析

在这里插入图片描述

题目给定一个数组nums,和k;其中nums中每一个数字不是1就是0

k表示最多可以反转(01)的次数。

要求我们返回操作之后,数组中连续1的最大个数。

什么意思呢?

就是我们最多可以将k个数组中的0变成1,然后我们要找出进行变换操作后数组中连续1的最大个数。

算法思路

这里,做出来本道题的关键就在于如何处理这个最多k01的问题。

这里我们真的要将0变成1?

显然是不可以的,这里数组有很多子数组,变了之后如何接着进行呢?

所以我们不能这样操作,就只能另辟蹊径:

这道题也是最长子数组问题,那也是要用滑动窗口的;我们想一想如何将其变成这样的问题?

很显然,这里题目要求最多可以进行k次变换,我们不能进行真的变化操作。

就只能在原数组中找到一个子数组(其中0的个数不超过k);

这样这个问题就变成了我们熟悉的找子数组的问题,这里条件就是区间内0的个数不超过k

所以思路就简单明了了。

这里定义一个变量zero来记录区间[left , right]0的个数。

入窗口:right向右遍历,遇到0就更新zero

出窗口:如果zero>k就表示当前区间内不满足条件了,就进行出窗口操作;如果nums[left] == 0,就更新zero

更新结果:这里更新结果还是在出窗口之后,出窗口之后区间是满足条件的并且每次入窗口之后不一定会出窗口,这样更新就不会漏掉每一种情况。

代码实现

class Solution {
public:
    int longestOnes(vector<int>& nums, int k) {
        int n = nums.size();
        int left = 0, right =0;
        int zero = 0;
        int ret = 0;
        while(right<n)
        {
            //入窗口
            if(nums[right++] == 0)
                zero++;
            //出窗口
            while(zero>k)
            {
                if(nums[left++]==0)
                    zero--;
            }
            //更新结果
            ret = max(ret,right - left);
        }
        return ret;
    }
};

这里第一次接触到滑动窗口,感觉有一点点抽象;

这里滑动窗口其实就是同向双指针,只是在遍历的过程中维护了一段区间就像一个窗口一样在数组中滑动。


网站公告

今日签到

点亮在社区的每一天
去签到