什么!滑动窗口原来如此丝滑

目录

1.⻓度最⼩的⼦数组

2.⽆重复字符的最⻓⼦串

3. 最大连续 1 的个数 III

4.将 x 减到 0 的最⼩操作数

5.找出字符串中所有字母异位词

6.水果成篮

7.最小覆盖字串


1.⻓度最⼩的⼦数组

滑动窗口的本质就是同向双指针,它也有一个固定的模板,分为进窗口,判断,出窗口,然后再特定的位置更新结果.

解法思路:本题我们使用滑动窗口来解决问题,滑动窗口本质上就是同向的双指针。我们来分析题目,求最短的子数组之和满足大于等于target。我们观察,我们首先定义letf = 0,right = 0 ,将开头定在最左边,right也定义在最左边,然后移动right,求left到right之间的数组之和,当他们之间的和大于等于target的时候,这时的长度有可能时最短长度,所以我们需要与上次(我们定义一开始的最短长度为 INT_MAX)的长度所比较,如果比上一次的短,那么我们就需要更新最短长度。此时我们的left和right之间的数组之和是满足题目要求的,如果我们继续让rihgt++ ,只会是数组更大 ,长度更长,似乎没意义,所以我们需要缩短left和rihgt之间的差距,就要让left++,再次观察left和right之间的数组之和是否满足题目要求。满足我们就可以更新最小长度,如果不满足我们就让rihgt++,如此循环便可以得到最短的长度,这就像一扇窗户一样,在不停的拉开,和关上

代码示例:

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int ret = INT_MAX; //定义初始的最小值是INT_MAX 保证第一次比较会改变长度的值
        int left = 0,right = 0;
        int sum = 0;
        while(left<=right && right<nums.size())
        {
       
            sum+=nums[right];
            while(sum>=target)//  当满足条件时,就跟新长度
            {
                ret = min(ret,right-left+1);
                sum-=nums[left++];
            }

            right++;
        }
       
       return ret==INT_MAX ? 0 : ret;
    }
};

2.⽆重复字符的最⻓⼦串

解题思路:

这道题如果用暴力解法的话,就是一个双循环,就可以找到最长的数组,但是这道题目用双循环会超时,所以我们想一下优化的解法,就是滑动窗口。

我们首先定义一个left,和一个right指向字符串的开头,然后让right++,直到right指向重复的字符,我们为了方便可以利用hash表来记录字符出现的次数,当在hash表中有字符出现两次时,我们就需要移动left,并且将left所对应的字符从hash表中去除,也就是出窗口,直到left移动到重复字符的后面,hash表中right所对应的字符就只出现了一次,这是也就说明又没有重复字符了,我们就继续移动right。然后更新最长的长度。

代码示例:

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int hash[128] = {0}; //定义一个仿hash表
        int left = 0,right = 0; 
        int ret = 0;

        while(right<s.size()) //结束条件right到达结尾
        {
            hash[s[right]]++;  // 将right所对应的字符对应到hash表中,并且将次数加1;

            while(hash[s[right]]>1)  // 当right所对应的字符出现两次,就需要出窗口了
            {
                hash[s[left++]]--; // 出窗口操作,将left所对应的字符在hash表中去除,直到left            
                                   //移动到重复字符的后面,此时重复的元素的次数就减去一次,我
                                   //们就可以重新开始寻找没有重复字符的最长字串了
            }
            ret = max(ret,right-left+1); //更新最大值
            right++;// 让right继续下去;
        }

        return ret;
    }
};

解法思路:

不要把这个题目想复杂,可以反转k个整数,不就是在一个全是1的数组中可以有多少了0吗?这样就可以把问题化简。

我们利用滑动窗口算法,定义一个left和一个right指向数据开头,我们可以利用一个计数器count来记录0出现的次数,让right向后移动遇到0的时候就让count++,知道count的值大于k时,我们就需要让left++,然后进行判断,如果left对应是1,就继续让left++,当遇到0时,就让count--,直到count小于等于k,然后更新返回的结果

代码示例:

class Solution {
public:
    int longestOnes(vector<int>& nums, int k) {
        int count = 0;//计数器
        int ret = 0;           //返回值
        int left =0 ,right = 0; // 左右指针
        while(right<nums.size()) 
        {   
             if(nums[right]==0) // 当right==0 ,计数器++
             count++;

            while(count>k) // 判断,当count>k时,就需要让left向右移,直到count小于等于k
            {
                if(nums[left]==0)
                {
                    count--;                    
                }
                left++;
            }
            ret = max(ret,right-left+1);//更新返回值

            right++;//右边的指针++;
        }
        return ret;
    }
};

4.将 x 减到 0 的最⼩操作数

解题思路:

这道题目我们发现按照题目的要求这么取做的话,我们用肉眼发现是很简单的,但是代码描述却是十分的复杂,所以我们“正难则反”,我们观察发现这道题目可以变为求最长子数组,使得这个子数组之和等于数组之和(Sum)减去x。当这个子数组最长,并且子数组之和时Sum-x;那么这正好时去除了两边最少的个数并且满足相减为0;这样就简单了,详细解释在代码中

代码示例:

class Solution {
public:
    int minOperations(vector<int>& nums, int x) {
        int n  = nums.size(),Sum = 0;// 将数组长度赋给n,定义Sum记录数组之和
        for(auto e: nums)//求数组所有元素之和
        {
            Sum+=e;
        }
        int target = Sum - x;// 将Sum-x就是子数组需要满足的条件 定义为target
        int len = -1;// 起始长个数为-1;
        int left = 0, right = 0; // 利用滑动窗口 
        int sum = 0;// 这个sum是用来求子数组之和的
        if(target<0) //特殊情况:当target<0 也就是整个数组之和都没有x大,那么不存在可以满足的                                    
        return -1;  //条件,直接返回-1;
        

        while(right<n) // 开始遍历数组
        { 
            sum+=nums[right]; // 将right指向的元素加入sum,求left到right之间子数组的和

            while(sum>target)//判断 当sum > target时,说明数组已经过长,并且在left和right之                                                            
                             //间不存在满足的子数组,让right继续向后走没有意义;
            {                //所以更新一下子数组,从sum中减去left所指元素,让left++; 这样
                             // 子数组的个数就更新了
                sum-=nums[left++];
            }

            if(sum==target)//只有当出现sum等于targer时,才能满足条件,然后更新结果
            {
               len = max(len,right-left+1);//记录下最长数组长度
            }
            right++;
        }

          //注意,返回值时,我们求的时最长的子数组但是题目要求时最小的操作次数,所以我们返回值            
          //时,需要用数组长度减去最长子数组长度
        if(len == -1)
        return -1;
        else
        return n-len;
       
    }
};

5.找出字符串中所有字母异位词

  这道题目最简单是通过滑动指针和hash表来实现,直接开始题目解析

题目解析:

 题目中要求我们找出所有关于p的异位词,异位词就是指p中的所有字符随机组合成的一个新的字符串,我们在这提出一个思路,既然是异位词,那么就包含p中的所有字母,那我们就将题目转化为在s字符串中找到一个子字符串,这个子字符串中的字符数量和字符种类要和p中的要相同就可以了,这样我们就将题目变简单了

详细解释在代码中解释

class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        int count = 0//用于计算在窗口中的有效字符数
        int hash1[26]={0};//记录p中的对应字符数量
        vector<int> ret;//返回的数组
        int hash2[26]={0};// 记录在窗口中的对应字符数量
        for(auto e: p) //将p中的所有字符和数量记录在hash1中
        {
            hash1[e-'a']++;
        }
          //开始查找
        for(int left=0, right =0;right<s.size();right++)
        {
            char in = s[right];//待进窗口字符
            hash2[in-'a']++;//进窗口

            if(hash2[in-'a']<=hash1[in-'a']) //如hash1中的对应字符数量大于hash2中的对应字                               
                                             //数(因为是先进了窗口,所以要加上等于),如何是         
                                             // 一个无效字符,他一定不在hash1中,所以我们就 
                                             //不需要将有效位数量count++,他在最后出窗口时也 
                                             //一定会被出掉的
            {
                count++;//有效字符就加1,
            }          
            if(right - left +1 > p.size())  //判断  当right和left之间的值大于p的数量时,就                
                                            ////代表窗口中的元素过多,需要出窗口                                                                                        
            {
                char out = s[left]; //待出窗口元素
                if(hash2[out-'a'] <= hash1[out-'a']) //出窗口 当待出窗口元素数量小于等于                                                              
                                                     //hash1中的元素时我们就要将该元素从有        
                                                     //效字符数量中减去,然后出窗口
                        count--;
                        hash2[out-'a']--;
                left++;
            }

            if(count==p.size()) //当此时我们的有效字符数量和p的字符数量相等的时候,说                                                                    
                                //明我们找到了一个异位词,将起始位置left翻入ret中
            {
                ret.push_back(left);
            }

        }
        return ret;
    }
};

6.水果成篮

题目解析:

       我们经过读题可以发现,题目要求我们的两个篮子里中的每个篮子的水果种类要是相同的,也就是我们所能采摘的水果只能是两种,所以我们就将题目转化为了在一个数组中,只有两种数字的最大长度,显然,这题的解题思路还是滑动窗口

代码示例:

class Solution {
public:
    int totalFruit(vector<int>& fruits) {
        unordered_map<int,int> hash;//用一个hash表来记录种类和数量
        int ret = 0;
        for(int left = 0,right =0 ;right<fruits.size();right++)
        {
             hash[fruits[right]]++;//进窗口  将该种类放入hash表中
           
            if(hash.size()>2)//判断如果种类大于两种了,就需要出窗口
            {  
                while(hash[fruits[left]] >1) //如果当前品种的数量大于1,我们就需要将他减去,并 
                                             //且将他从窗口里出掉
                {
                    hash[fruits[left]]--;//减去一个数量
                    left++;//出窗口
                }

              
                hash.erase(fruits[left]);//到这里的时候,种类数量还多余两个,此时left所指向     
                                         //的种类的个数一定只剩一个了,所以我们需要将他从        
                                         ////hash表中去除
                left++;
            }

            ret = max(right-left+1,ret);//更新结果

        }
        return ret;
    }
};

7.最小覆盖字串

题目解析:
   这道题目要求我们找出s中涵盖t所有字符的最小字串,并且在这个最小子串中各种字符的数量必须是大于等于t中的对于字符的数量,并且最小的字串只有一个。对于最小字串的题目,显然还是我们的滑动窗口算法。我们可以利用 例5 中的思路来解决这道题目,具体解法思路放在代码中。

代码示例:

class Solution {  //与本篇博客第五题思路类似
public:
    string minWindow(string s, string t) {
        int hash1[128] = { 0 };//统计t中的字符出现数量,为了提高效率不使用STL容器
        int kinds = 0;//用于统计t中的字符种类
        for(auto e : t)//统计t中的字符数量以及字符种类
        {
            if(hash1[e]==0) kinds++;//当我第一次遇见这个字符就将kinds加1
            hash1[e]++;//对于字符数量加1
        }        

        int minlen = INT_MAX; int begin = -1;//用于返回,记录最小长度以及起始位置
        int hash2[128] = { 0 };//记录窗口中的个字符数量
        for(int left = 0, right = 0,count = 0;right<s.size();right++)//用count来记录有效的                                                
                                                  //字符种类,注意这里与例5不同的是不再是记录        
                                                 //数量,而是以及满足了的字符种类数量(看不懂    
                                                 //继续往下看)
        {
            char in = s[right]; //进窗口
            hash2[in]++;//对于元素再hash2中+1
            if(hash2[in] == hash1[in]) count++;//注意 我们只有当hash2中的元素数量与hash1中的 
                                               //对应元素数量相同时,说明这时窗口内的该元   
                                 //素数量达到了要求,所以我们让count++,说明满足了其中一个元素

            //判断
            while(count==kinds)//当count的种类数量和t中的字符种类数量相同的时候,说明此时是一个     
                               //满足条件的窗口,这时我们需要更新结果,然后出窗口,继续寻找下一 
                               //个满足条件的范围区间
            {
                if(right-left+1 < minlen)//当此时的区间范围下雨minlen时更新
                {
                    minlen=right-left+1;
                    begin = left;
                }

                char out = s[left];//出窗口
                left++;
                if(hash2[out]==hash1[out]) count--;// 如果此时出窗口的元素的数量和t中对应元素    
                                                   //的数量是相等的,将此元素移除后,该元素的     
                                                   //数量就不再符合要求,所以count就需要-1
                hash2[out]--;
            }
        }
            if(begin==-1) return "";//返回结果,如果起始位置没有变化说明没有符合的条件,返回空     
                                   //字符串,起始位置发生变化,则返回s的字串。
            else return s.substr(begin,minlen);
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

晚风吹长发

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值