1.滑动窗口问题

核心思想:

  • 滑动窗口的核心思想是使用两个指针(或索引)来标记窗口的左右边界,并动态调整窗口的大小和位置,以在一次遍历中解决问题。窗口在遍历过程中“滑动”或者“移动”,使得算法能够在数据结构中找到所需的子序列,而无需反复遍历相同的子序列。

处理思路:

  • 通常,窗口的初始大小为 0,即两个指针都指向数据结构的起始位置。

  • 右指针向右移动,扩展窗口的右边界,包含新的元素。

  • 在每次右指针移动后,检查当前窗口是否满足特定条件(如无重复字符、窗口内的元素和满足某个值等)。如果满足条件,可以记录或更新结果。

  • 如果窗口不再满足条件,则通过移动左指针缩小窗口的左边界,从而移除掉一些元素。这一步用于调整窗口的大小,使其重新满足条件。

  • 右指针继续移动,重复上述步骤,直到遍历完所有元素。

适用场景:

  • 找到长度固定或可变的最大/最小子数组(或子串)。

  • 计算具有特定属性的子数组(如和、乘积等)。

  • 查找满足特定条件的最长/最短子数组(或子串)。

案例解析:

力扣num-3:

题目描述:给定一个字符串string,要求找出不含重复元素的最长字串的长度。

  • 字符串“ abcdeafg”,这个字符串的最长不重复子串为“ abcde ”。

  • 使用滑动窗口的思想,left 和 right 指针初始都指向字符串的开头。right 用于扩展窗口,left 用于收缩窗口以排除重复字符。

  • 右指针 right 在每次循环中向右移动,并将当前字符加入哈希表,记录其出现的次数。在右指针移动之后,检查当前字符在哈希表中的次数。如果该字符的次数大于 1,说明在当前窗口中有重复字符。

  • 当发现重复字符时,开始通过移动左指针 left 缩小窗口,同时将 left 指向的字符从哈希表中移除。左指针继续移动,直到窗口内不再包含重复字符为止。

  • 例如,对于字符串 abcdeafg,当右指针移动到第二个 a 时,发现 a 已经存在于哈希表中,因此需要移动左指针,直到移除掉第一个 a。

  • 每次调整完左指针并且窗口内无重复字符后,计算当前窗口的长度(right - left),并与 ret 比较,更新 ret 为较大值。

  • 右指针继续向右移动,重复上述过程,直到遍历完字符串的所有字符。

class Solution {
public:
    int lengthOfLongestSubstring(string s)
    {
        int tmphash[128]={0}; //记录每个字符出现次数的哈希表。
        int left=0; //左指针
        int right=0; //右指针
        int n=s.size(); //字符串长度
        int ret=0; //最长不重复子串长度
        while(right<n) //当右指针不超出字符串区间,持续循环
        {
            tmphash[s[right]]++; //将右指针指向的元素在哈希表中的数量加一
            while(tmphash[s[right]]>1) //如果元素出现在哈希表的数量大于一,说明重复出现
            {
                tmphash[s[left++]]--; //出现重复元素,开始调整左指针,从左指针位置开始向后,直到找到重复元素,注意找到后left还会++,所以left会指向重复元素的后一个元素的位置。
            }
            right++; //使用的是while循环,所以要手动调整right指针的位置
            ret=max(ret,right-left); //更新子串最大长度
        }
        return ret; //返回子串最大长度
    }
};

力扣num-1004:

题目描述:给定一个二进制数组 nums 和一个整数 k,如果可以翻转最多 k 个 0 ,则返回 数组中连续 1 的最大个数 。

  • 比如数组为:[1,1,1,0,0,0,1,1,1,1,0],且k的值为2。那么翻转下标为4和5的两个0,最长的连续1的数量为6。或者翻转下标为5和10的两个0,最长的数量也是6。

  • 这个题可以翻译为找到最长的连续0。所以可以使用滑动窗口解题。

class Solution {
public:
    int longestOnes(vector<int>& nums, int k)
    {
        int left=0;
        int right=0;
        int num=0;
        int ret=0;
        
        while(right<nums.size()) //循环
        {
            if(nums[right]==0) num++; //如果right下标为0,就将0的计数器+1
            while(k<num) //如果0的数量超过k,挪动left,将0的计数器维持在不大于k,也就是小于等于k。
            {
                if(nums[left++]==0) num--; 
            }
            right++; //while循环,需要手动挪动right指针
            ret=max(ret,right-left); //计算当前最大连续1的数量
        }
        return ret;
    }
};

力扣num-1658:

题目描述:给你一个整数数组 nums 和一个整数 x 。每一次操作时,你应当移除数组 nums 最左边或最右边的元素,然后从 x 中减去该元素的值。请注意,需要 修改 数组以供接下来的操作使用。

  • 理解“ 正难则反 ”的理念。这个题正面想比较难解,就反向思考。

  • 将题目转换为在数组中找到一个最大子数组,使得这个数组的和等于sum(nums) - x 。这样变相就能够得到左右两边要删去哪些数。

class Solution {
public:
    int minOperations(vector<int>& nums, int x)
    {
        int sum=accumulate(nums.begin(),nums.end(),0); //计算vector的和
        int target=sum-x; //计算目标数组的和
        int n=nums.size(); //数组大小
        if(target<0) 
            return -1;
        if(target==0) //处理tmp==sum的情况
            return n;

        int left=0;
        int right=0;
        int tmp=0;
        int ret=-1;

        for(;right<n;right++)
        {
            tmp+=nums[right];
            while(tmp>target)
            {
                tmp-=nums[left++];
            }
            if(tmp==target)
            {
                ret=max(ret,right-left+1); //更新长度
            }
        }
        if(ret>0) return n-ret;
        return ret;
    }
};

力扣num-904:

题目描述:在数组中找到一个连续区域,使得这个连续区域只包含两中元素,且这两个元素在数组中占比最大。比如{1,2,2,3,2,3,3,4}中最大的连续数组就是{2,2,3,2,3,3}。在比如{1,2,3,4,4,5,5,6}中就是找到{4,4,5,5}。

  • 本质上就是找最长子数组。要求是数组中最多不超过2种(1种或2种)元素。

  • 通过unordered_map创建哈希表,使得既能够存储元素的种类,又能够存储元素的数量。

  • 挪动右指针向哈希表添加元素,每次添加后都更新最长子数组的长度。

  • 当哈希表中的元素大于两种就挪动左指针。同时对哈希表中的元素的数量做减少,如果某个元素的数量减少到0,删除这个元素,此时哈希表的元素数量再次被维护到两种。

  • 直到右指针遍历完整个数组,返回最长需求子数组的长度即可。

class Solution {
public:
    int totalFruit(vector<int>& fruits)
    {
        unordered_map<int,int> hash; //创建哈希表
        int left=0;
        int right=0;
        int ret=0;

        while(right<fruits.size())
        {
            hash[fruits[right]]++; //哈希表入元素
            while(hash.size()>2) //哈希表元素维持在2
            {
                hash[fruits[left]]--; //超过两种,开始挪动左指针直到删去一种
                if(hash[fruits[left]]==0)
                {
                    hash.erase(fruits[left]);
                }
                left++;
            }
            right++;
            ret=max(ret,right-left); //更新最大长度。
        }

        return ret;
    }
};

力扣num-438:

题目描述:给定两个字符串a和b,找到a中所有b的异位词子串。返回子串的索引,不考虑返回的顺序。比如abab中找ba,就返回{0,1,2}。

  • 首先思考如何比较两个字符串是否为异位字符串?将两个字符串都对丢到哈希表中,进行比较。

  • 暴力解法:在字符串a中,依次找到长度为字符串b长度的子字符串,比较两个字符串是否相等,相等则在一个vector中记录这个子串的起始下标。最后返回这个vector。

  • 优化解法:比如有一个字符串 “abcdacbefg”,找到所有 “abc”的异位子字符串,找到第一个异位子字符串下标为0,此时无需清空哈希表,而是直接将下标0位置字符出哈希表,将下标3字符入哈希表,再次比较即可。

class Solution {
public:
    vector<int> findAnagrams(string s, string p)
    {
        char hash1[26]={0}; //创建哈希表1,存储p字符串
        for(auto a: p) hash1[a -'a']++;
        
        vector<int> ret; //返回数组
        if(p.size()>s.size()) return ret; //如果p的长度大于s,表示s没有子串和p等长,直接返回即可
        
        int left=0;
        int right=0;
        char hash2[26]={0}; //哈希表2
        for(;right<p.size()-1;right++) hash2[s[right]-'a']++;
        
        while(right<s.size())
        {
            hash2[s[right]-'a']++;//入窗口
            if(right-left+1>p.size()) //窗口大小大于p串大小,出窗口
            {
                hash2[s[left]-'a']--;
                left++;
            }
            
            int flag=1;
            for(int i=0;i<26;i++) //比较两个串是否相同,根据哈希表存储的串中每个字符的数量
            {
                if(hash1[i]!=hash2[i])
                {
                    flag=0;
                }
            }
            
            if(flag==1) //如果串相同,则尾插left位置到返回值数组
            {
                ret.push_back(left);
            }
            right++;
        }
        return ret;
    }
};

  • 12
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
单调队列是一种特殊的数据结构,用于解决滑动窗口问题。在滑动窗口问题中,要求在一个大小为k的窗口中,找到窗口内的最大值或最小值。单调队列通过维护一个单调递增或递减的队列,可以高效地解决这个问题。 具体实现上,可以使用两个单调队列,一个用于维护窗口内的最大值,另一个用于维护窗口内的最小值。在添加新元素时,如果队列不为空且要加入的元素值小于队列尾的值,则将队尾弹出,直到队尾小于要加入的元素或者队列为空。这样,队首就是当前窗口内的最大值或最小值了。 对于问题中提到的代码,首先定义了一个用于存储下标的队列stk,以及用于存储数组元素的数组a。然后通过一个循环,分别计算了窗口内的最小值和最大值。在计算最小值时,维护了一个单调递增的队列,即队首是窗口内的最小值。在计算最大值时,维护了一个单调递减的队列,即队首是窗口内的最大值。最后,输出计算结果。 总结起来,单调队列是一种解决滑动窗口问题的高效数据结构,可以通过维护单调递增或递减的队列来求解窗口内的最大值或最小值。<span class="em">1</span><span class="em">2</span><span class="em">3</span><span class="em">4</span> #### 引用[.reference_title] - *1* [整数滑动窗口(acwing算法题C++)](https://blog.csdn.net/falldeep/article/details/118631830)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatgptT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [【Week5作业 D】滑动窗口【单调队列】](https://download.csdn.net/download/weixin_38647039/14916076)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatgptT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* *4* [【acwing】单调队列--154. 滑动窗口](https://blog.csdn.net/qq_43531919/article/details/115402227)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatgptT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

S+叮当猫

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

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

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

打赏作者

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

抵扣说明:

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

余额充值