滑动窗口算法详解

目录

概念

例题:

一、长度最小的子数组

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

三、最大连续1的个数 III

四、将 x 减到 0 的最小操作数

五、水果成篮 

六、找到字符串中所有字母异位词

七、串联所有单词的子串​​​​​​


概念

  滑动窗口,就是一个会滑动的窗口...呃....这么说估计会被骂....但是,算法不是公式,也不是套路,不是一个模板。而是一种思路,一种优化解决问题的思路。所以,生硬的去总结和概括,那是生硬的,难以接受的,学不会的。不同的算法,是对应不同的解决问题的方法策略。一个问题的解决,其策略是唯一的吗?不是,只有比较合适,或者比较不合适。所以,算法无常势。某个具体的算法,你需要学会的是其思想,其解决问题的切入点。我们要培养的,是多角度的切入点,多策略的思考。当你学了某个算法,你能对这种解决方式有个底。那么当你下次再遇到类似的问题,你就会敏感,就会触类旁通。然后根据思想,设计具体的解题过程。

   所以,下面跟我来做一个系列的题目,这一系列的题目都将使用滑动窗口的思路进行解决,自己体会。注意我是如何分析,如何解决,如何根据思路实现代码的转换。

一、长度最小的子数组

LCR 008. 长度最小的子数组 - 力扣(LeetCode)

思路:

class Solution {
public:
   int minSubArrayLen(int target, vector<int>& nums) {
    int n = nums.size();
    int left = 0, right = 0;
    int len = n, sum = 0;

    while (right < n)
    {
        sum += nums[right];
        while(sum >= target)
        {
            sum -= nums[left];
            len = min(len,right - left + 1);
            ++left;
        }
        ++right;
        
    }
        if(left == 0)
        {
            return 0;
        }

    return len;

}
};

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

LCR 016. 无重复字符的最长子串 - 力扣(LeetCode)

思路:

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int left = 0, right = 0, n = s.size();
        int hash[128] = {0};
        int len = 0;
        while(right  < n)
        {
            //进入窗口
            hash[s[right]]++;//记录进入的值
            while(hash[s[right]] > 1)//判断是否重复,重复就是2
            {
                //删除对应的值
                hash[s[left]]--;
                ++left;
            }
            //到这里,说明删除重复值完毕,继续走
            len = max(len, right - left + 1);
            ++right;            
        }
        return len;

    }
};

三、最大连续1的个数 III

1004. 最大连续1的个数 III - 力扣(LeetCode)

思路:题目转化为:寻找最大子数组,但是0的个数小于k

class Solution {
public:
    int longestOnes(vector<int>& nums, int k) {
        //最大子数组,0不超过k
        //滑动窗口解决
        int left = 0, right = 0, len = 0,zero = 0, n = nums.size();
       while(right < n)
       {
            //判断进窗口
            if(nums[right] == 0)
                zero++;
            right++;


            //判断出窗口
            while(zero > k)
            {
                if(nums[left] == 0)
                    zero--;
                ++left;
            }
            //更新结果
            len = max(len,right - left);
       }

       if(k >= n)
            return n;
        
        return len;

    }
};

四、将 x 减到 0 的最小操作数

1658. 将 x 减到 0 的最小操作数 - 力扣(LeetCode)

思路:

//只能移动两边

    //可以转化为在中间寻找最长子数组,

    //该数组的值正好为nums的和-x

class Solution {
public:
    int minOperations(vector<int>& nums, int x) {
        //只能移动两边
        //可以转化为在中间寻找最长子数组,
        //该数组的值正好为nums的和-x
        int len=-1, left=0, right=0;
        int sum=0, tmp=0, n=nums.size();
        for(auto e : nums)
            sum += e;

         if(x > sum)
            return -1;
        sum -= x;

        while(right < n)
        {
            //进窗口
            tmp += nums[right];
            ++right;

            //判断出窗口
            if(tmp > sum)
            {
                while(tmp > sum)
                {
                    tmp -= nums[left];
                    ++left;
                }
            }
            //更新结果
            if(tmp == sum)
            len = max(len, right - left);
        }

        if(len == -1)
            return -1;
        return n - len;

    }
};

五、水果成篮 

904. 水果成篮 - 力扣(LeetCode)

思路:

   //转化为求最大子数组

        //该数组最多不超过2个元素

        //使用滑动窗口

class Solution {
public:
    int totalFruit(vector<int>& fruits) 
    {
        //转化为求最大子数组
        //该数组最多不超过2个元素
        //使用滑动窗口
        int left=0,right=0,len=0,n = fruits.size();
        unordered_map<int,int> hash;
        while(right < n)
        {
            //进窗口
            hash[fruits[right]]++;
            right++;
            
            //出窗口
            while(hash.size() > 2)
            {
                hash[fruits[left]]--;
                if(hash[fruits[left]] == 0)
                {
                    hash.erase(fruits[left]);
                }
                ++left;

            }

            //更新结果
            len = max(len, right - left);

        }
        return len;



    }
};

六、找到字符串中所有字母异位词

LCR 015. 找到字符串中所有字母异位词 - 力扣(LeetCode)

思路:

  //转化为寻找固定长度子数组匹配问题

        //哈希表记录子串用于比较

        //用滑动窗口

class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        //转化为寻找固定长度子数组匹配问题
        //哈希表记录子串
        //用滑动窗口
        int n = p.size();
        int left=0, right=0;
        unordered_map<char,int> hash1;
        unordered_map<char,int> hash2;
        vector<int> ret;
        for(auto e : p)
        {
            hash1[e]++;
        }

        while(right < s.size())
        {
            //进窗口
            hash2[s[right]]++;
            ++right;

            //出窗口
            if(right - left > n)
            {
                hash2[s[left]]--;
                if(hash2[s[left]] == 0)
                    hash2.erase(s[left]);
                ++left;
            }

            //更新结果
            if(hash1 == hash2)
                ret.push_back(left);
        }

        return ret;
    }
};

七、串联所有单词的子串​​​​​​

30. 串联所有单词的子串 - 力扣(LeetCode)

思路:这一题,有一点难度,但是,也只是把单词匹配换成了字符串匹配问题。单词的比较比较简单,但是字符串的比较不太容易。因此,我们转变思路,设置一个计数器,当插入一个单词时,查看哈希表,看是否存在,有就count++,其余不管。当时count计数与单词个数相同时间,说明匹配,记录相关位置。这是一个比较有意思的题,可以好好的琢磨。问题转化的能力,容器的使用,代码运行过程细节处理,很考验人的。

class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {

        int n = words[0].size(), m = words.size();
        vector<int> ret;
        unordered_map<string, int> hash1;
        
        //
        for(auto& e : words)
            hash1[e]++;

        for(int i = 0; i<n; ++i)
        {
            int left = i;
            int right = i;
            int count = 0;
            unordered_map<string, int> hash2;
            while(right < s.size())
            {
                //进窗口
                string in = s.substr(right, n);
                hash2[in]++;

            if(hash2[in] <= hash1[in])//小于等于,表示hash2[in]是hash1[in]的一部分
                    count++;

                //判断
                if(right - left + 1 > n * m)
                {
                    string out = s.substr(left, n);
                    if(hash2[out] <= hash1[out])
                        count--;
                    hash2[out]--;
                    left += n;
                }

                //检查更新
                if(count == m)
                    ret.push_back(left);

                right += n;
            }
        }
        return ret;
    }
};

  • 18
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

二十5画生

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

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

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

打赏作者

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

抵扣说明:

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

余额充值