算法—滑动窗口

目录

长度最小的子数组

无重复字符的最长子串

[最大连续1的个数 III](#最大连续1的个数 III)

[将 x 减到 0 的最小操作数](#将 x 减到 0 的最小操作数)

水果成篮

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

串联所有单词的子串

最小覆盖子串


注意:滑动窗口的本质其实就是同向的双指针

滑动窗口的使用:

长度最小的子数组

**思路:**定义 left,right 两个指针,这两个指针指向的元素以及它们中间的元素构成了一个窗口,两指针初始时都指向下标为 0 处,当窗口中数据总和小于 target 时,右移 right 增大窗口中的数据总和,直到大于等于 target 时,窗口满足题目要求,计算它的长度保存下来,然后右移 left 来减小窗口数据总和,判断是否仍然大于等于 target,如果满足要求,更新结果,不满足继续右移 right。

**为什么使用滑动窗口能解题:**首先使用暴力枚举的方式一定可以找到正确的结果,而滑动窗口就是利用数组的单调性省去了很多没有必要枚举的部分,比如当窗口内数据总和第一次大于 target 后,此时在右移 right 没有意义了,因为再去向窗口增加更大的值一定满足条件,但是元素数据增多,一定不是长度最小的子数组,所以,以下标为 0 的元素作为起始的所有情况就都已经考虑到了,只不过后面的情况一定不是答案,直接略过,然后右移 left ,以下一个元素为起始继续枚举,如果符合就更新结果,不符合就继续向窗口中增加元素。

代码:

cpp 复制代码
class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int left = 0;
        int right = 0;
        int ret = INT_MAX;
        int sum = 0;
        while(right < nums.size()){
            sum += nums[right++];
            while(sum >= target){
                ret = min(ret, right - left);
                sum -= nums[left++];
            }
        }

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

无重复字符的最长子串

思路:滑动窗口+哈希表(用来判断字符是否重复)。

**过程:**还是定义 left,right 两个指针,将 right 指向的元素丢入哈希表,判断是否重复,不重复就更新结果,然后继续向右移动 right 指针,让剩余字符进窗口,如果重复了,就将当前 left 指针指向的字符从哈希表中删除,并右移 left 指针,直到不重复。

代码:

cpp 复制代码
class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int left = 0;
        int right = 0;
        int hash[128] = {0};
        int ret = 0;
        while(right < s.size()){
            hash[s[right]]++;
            while(hash[s[right]] > 1){
                hash[s[left]]--;
                left++;
            }
            ret = max(ret, right - left + 1);
            right++;
        }

        return ret;
    }
};

最大连续1的个数 III

思路: 本题因为有翻转 0 的限制,所以可以将问题从找连续的 1 转化为找出最长子数组,0 的个数不超过 k 个。

**过程:**定义 left,right 两个指针和一个记录窗口中 0 个数的计数器,向窗口中增加元素时判断是否为 0,如果是 0,计数器自增 1,否则不变,然后判断 0 个数是否超过 k,超过就减小窗口,直到 0 个数符合要求,当 0 个数符合要求后更新结果即可。

代码:

cpp 复制代码
class Solution {
public:
    int longestOnes(vector<int>& nums, int k) {
        int left = 0;
        int right = 0;
        int ret = 0;
        int count = 0;
        while(right < nums.size()){
            if(nums[right++] == 0) count++;
            while(count > k){
                if(nums[left] == 0) count--;
                left++;
            }
            ret = max(ret, right - left);
        }

        return ret;
    }
};

将 x 减到 0 的最小操作数

思路: 直接解题因为可能会用到数组左边的元素,也可能会用到数组右边的元素,这些数据不连续,不好解题,所以对问题进行转化。定义数组元素之和为 sum,将问题转化为找出最长子数组,其元素之和正好等于sum - x。

代码:

cpp 复制代码
class Solution {
public:
    int minOperations(vector<int>& nums, int x) {
        int left = 0;
        int right = 0;
        int sum = 0;
        for(auto num : nums){
            sum += num;
        }
        int target = sum - x;
        if(target < 0) return -1;
        int ret = -1;
        sum = 0;
        while(right < nums.size()){
            sum += nums[right++];
            while(sum > target){
                sum -= nums[left++];
            }
            if(sum == target){
                ret = max(ret, right - left);
            }
        }

        return ret == -1 ? -1 : nums.size() - ret;
    }
};

水果成篮

**思路:**本题本质其实就是找出一个最长子数组,且子数组中不超过两种类型的水果。使用滑动窗口 + 哈希表就可以解决问题,其中哈希表的作用是确保窗口中水果种类不超过2。

代码:

cpp 复制代码
class Solution {
public:
    int totalFruit(vector<int>& fruits) {
        int left = 0;
        int right = 0;
        unordered_map<int, int> hash;
        int ret = 0;
        while(right < fruits.size()){
            hash[fruits[right++]]++;
            while(hash.size() > 2){
                hash[fruits[left]]--;
                if(hash[fruits[left]] == 0) hash.erase(fruits[left]);
                left++;
            }
            ret = max(ret, right - left);
        }

        return ret;
    }
};

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

思路: 滑动窗口 + 哈希表 + 利用变量count统计窗口中"有效字符"个数。

过程:

  • 将 p 串放入一个哈希表中(哈希表1)。
  • 入窗口:定义 left,right 两个指针,移动 right 指针向窗口增加字符,并将该字符放入另一个哈希表中(哈希表2),并且判断当前 right 指向的字符在哈希表2中的个数是否小于等于哈希表1中该字符的个数,如果小于等于,说明是有效字符,count++,否则 count 不变。
  • 判断 + 出窗口:如果窗口字符数量大于 p 串长度,移动 left 指针减少窗口中字符数量,同时维护哈希表2,判断移出去的字符在移除之前在哈希表2中的数量是否大于哈希表1中的数量,如果大于,说明是多余或者无效的字符,count 不变,否则 count--。
  • 更新结果:如果 count 等于 p 串的长度,说明是异位词,更新结果。

代码:

cpp 复制代码
class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        int hash1[26] = {0};
        for(auto ch : p) hash1[ch - 'a']++;

        int hash2[26] = {0};
        int left = 0;
        int right = 0;
        int count = 0;
        vector<int> ret;
        while(right < s.size()){
            hash2[s[right] - 'a']++;
            if(hash2[s[right] - 'a'] <= hash1[s[right] - 'a']) count++;
            right++;

            if(right - left > p.size()){
                if(hash2[s[left] - 'a'] <= hash1[s[left] - 'a']) count--;
                hash2[s[left++] - 'a']--;
            }

            if(count == p.size()){
                ret.push_back(left);
            }
        }

        return ret;      
    }
};

串联所有单词的子串

思路: 滑动窗口 + 哈希表 + 利用变量count统计窗口中"有效字符"个数。

**过程:**和上题非常类似,这里只说不同点。首先,哈希表维护的是字符串和数量的映射,其次,因为每次都要向哈希表中存一个字符串,所以 right,left 每次直接移动 words 中每个单词的长度个单位,最后,因为两个指针每次移动距离的增大,一次滑动窗口无法遍历所有情况,需要遍历 words中单词的长度次,例如下图 words 中每个单词长度为 3。

代码:

cpp 复制代码
class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        unordered_map<string, int> hash1;
        for(auto& s : words) hash1[s]++;

        int len = words[0].size();
        int m = words.size();
        vector<int> ret;
        for(int i = 0; i < len; i++){
            int left = i;
            int right = i;
            int count = 0;
            unordered_map<string, int> hash2;
            while(right + len <= s.size()){
                string str = s.substr(right, len);
                hash2[str]++;
                if(hash2[str] <= hash1[str]) count++;

                if(right - left + len > len * m){
                    string out = s.substr(left, len);
                    if(hash2[out] <= hash1[out]) count--;
                    hash2[out]--;
                    if(hash2[out] == 0) hash2.erase(out);
                    left += len;
                }
                
                if(count == m) ret.push_back(left);

                right += len;
            }
        }

        return ret;
    }
};

最小覆盖子串

思路:滑动窗口 + 哈希表 + 利用变量count统计窗口中"有效字符"种类。总体思路和上面两题一样,只不过这里 count 统计的是有效字符的种类,而不是数量,因为存在如下情况:

代码:

cpp 复制代码
class Solution {
public:
    string minWindow(string s, string t) {
        int hash1[128] = { 0 };
        int kinds = 0;
        for(auto ch : t)
            if(hash1[ch]++ == 0) kinds++;

        int hash2[128] = { 0 };
        int left = 0;
        int right = 0;
        int minlen = INT_MAX;
        int begin = -1;
        int count = 0;
        while(right < s.size()){
            char in = s[right];
            hash2[in]++;
            if(hash2[in] == hash1[in]) count++;

            while(count == kinds){
                if(right - left + 1 < minlen){
                    minlen = right - left + 1;
                    begin = left;
                }

                char out = s[left++];
                hash2[out]--;
                if(hash2[out] < hash1[out]) count--;
            }

            right++;
        }

        return begin == -1 ? "" : s.substr(begin, minlen);
    }
};
相关推荐
历程里程碑2 小时前
Linux 3 指令(3):进阶指令:文件查看、资源管理、搜索打包压缩详解
linux·运维·服务器·c语言·数据结构·笔记·算法
AI-小柒2 小时前
从零入门大语言模型(LLM):系统学习路线与实践指南
大数据·开发语言·人工智能·学习·信息可视化·语言模型·自然语言处理
hhy_smile2 小时前
Python environment and installation
开发语言·python
戌中横2 小时前
JavaScript 对象
java·开发语言·javascript
crossaspeed2 小时前
面向对象的三大特征和反射(八股)
java·开发语言
咋吃都不胖lyh2 小时前
GBDT 中的前向分布算法和贪婪学习
学习·算法
leo__5202 小时前
CLEAN算法仿真程序,用于雷达信号中的杂波抑制
算法
连山齐名2 小时前
程序员棋谱之一——单例模式
开发语言·单例模式
zfj3212 小时前
java synchronized关键字用法和底层原理
java·开发语言·轻量级锁·重量级锁·偏向锁·线程同步