优选算法专题2:滑动窗口

滑动窗口

目录

滑动窗口

试题一:长度最小的子数组

算法原理

代码编写

试题2:无重复字符的最长字串

算法原理

代码编写

试题3:最大连续1的个数III

算法原理

代码编写

试题4:将x减到0的最小操作数

算法原理

代码编写

试题5:水果成篮

算法原理

代码编写

试题6:找到字符串中所有字母异位词

算法原理

代码编写

试题7:串联所有单词的字串

算法原理

代码编写

试题8:最小覆盖子串

算法原理

代码编写


试题一:长度最小的子数组

算法原理

解法一:暴力枚举

暴力枚举出所有子数组的和

时间复杂度:O(N^3)

优化:在right遍历的同时,将结果加到sum中,可以省去求和遍历

时间复杂度:O(N^2)

解法二:滑动窗口

利用单调性,使用同向双指针来优化

left = 0,right = 0

right向右移动,进入窗口,计算窗口数据总和sum

sum小于target时,right++

sum大于等于target时,更新长度len,left++

更新窗口数据总和sum,再次判断,直到遍历完数组

时间复杂度:O(N)

本质:利用问题的特殊性质,砍掉重复的计算

代码编写

  • 个人版本
cpp 复制代码
class Solution 
{
public:
    int minSubArrayLen(int target, vector<int>& nums) 
    {
       int right = 0;
       int left = 0;
       int sum = 0;
       int n = nums.size();
       int len = 0;
       while(right < n)
       {
            sum += nums[right];
            while(sum >= target)
            {
                if(len == 0)
                {
                    len = right - left + 1;
                }
                if(len > right - left + 1)
                {
                    len = right - left + 1;
                }
                sum -= nums[left++];
            }
            right++;
       }
       return len;
    }
};
  • 标准版本
cpp 复制代码
class Solution 
{
public:
    int minSubArrayLen(int target, vector<int>& nums) 
    {
        int n = nums.size(),sum = 0,len = INT_MAX;
        for(int left = 0,right = 0;right < n;right++)
        {
            sum += nums[right];//进窗口
            while(sum >= target)//判断
            {
                len = min(len,right - left + 1);//更新结果
                sum -= nums[left++];//出窗口
            }
        }
        return len == INT_MAX ? 0 : len;
    }
};

试题2:无重复字符的最长字串

算法原理

解法一:暴力枚举 + 哈希表(判断字符是否重复出现)

时间复杂度:O(N^2)

解法二:滑动窗口

left = 0,right = 0

进窗口:让字符进入哈希表

判断是否出窗口:窗口内出现重复字符时出窗口

从哈希表删除该字符,循环到窗口无重复字符

更新结果

代码编写

  • 个人版本
cpp 复制代码
class Solution 
{
public:
    int lengthOfLongestSubstring(string s) 
    {
        unordered_set<char> hash;
        int left = 0;
        int right = 0;
        int n = s.size();
        int len = 0;
        while(right < n)
        {
            while(hash.count(s[right]))
            {
                hash.erase(s[left]);
                left++;
            }
            len = max(len,right - left + 1);
            hash.insert(s[right++]);
        }
        return len;
    }
};
  • 标准版本
cpp 复制代码
class Solution 
{
public:
    int lengthOfLongestSubstring(string s) 
    {
        int hash[128] = {0};//使用数组模拟哈希表
        int left = 0,right = 0,n = s.size();
        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;
    }
};

试题3:最大连续1的个数III

算法原理

转化为找出最长子数组,且0的个数不超过k个

解法一:暴力枚举

暴力枚举+zero计数器

解法二:滑动窗口

left = 0,right = 0

进窗口:如果right是1,right++;如果right是0,计数器加1,right++

判断是否出窗口:zero > k,left++,如果left是0,计数器减1

直到zero <= k ,窗口合法,窗口合法后更新结果

时间复杂度:O(N)

空间复杂度:O(1)

代码编写

  • 个人版本
cpp 复制代码
class Solution 
{
public:
    int longestOnes(vector<int>& nums, int k) 
    {
        int left = 0;
        int right = 0;
        int zero = 0;
        int num = 0;
        int n = nums.size();
        while(right < n)
        {
            if(nums[right] == 0)
            {
                zero++;
            }
            while(zero > k)
            {
                if(nums[left] == 0)
                {
                    zero--;
                }
                left++;
            }
            num = max(num,right - left + 1);
            right++;
        }
        return num;
    }
};
  • 标准版本
cpp 复制代码
class Solution 
{
public:
    int longestOnes(vector<int>& nums, int k) 
    {
       int ret = 0;
       for(int left = 0,right = 0,zero = 0;right < nums.size();right++)
       {
            if(nums[right] == 0)//进窗口
            {
                zero++;
            }
            while(zero > k)//判断
            {
                if(nums[left++] == 0)//出窗口
                {
                    zero--;
                }
            }
            ret = max(ret,right - left + 1);//更新结果
       }
       return ret;
    }
};

试题4:将x减到0的最小操作数

算法原理

正难则反:

转化为找出元素的和为sum - x(target)的最长(len)的子数组

n - len就是最小的操作数

解法:滑动窗口

left = 0,right = 0

进窗口:窗口值sum+=nums[right]

判断是否出窗口:当窗口值sum大于target,sum-=nums[left]出窗口

sum == target时更新结果

时间复杂度:O(N)

代码编写

  • 个人版本
cpp 复制代码
class Solution 
{
public:
    int minOperations(vector<int>& nums, int x) 
    {
        int right = 0;
        int left = 0;
        int total = 0;
        int ret = 0;
        for(auto e : nums)
        {
            total += e;
        }
        int target = total - x;
        int n = nums.size();
        int sum = 0;
        if(target < 0)
        {
            return -1;
        }
        if(target == 0)
        {
            return n;
        }
        while(right < n)
        {
            sum += nums[right];
            while(sum > target)
            {
                sum -= nums[left];
                left++;
            }
            if(sum == target)
            {
                ret = max(ret,right - left + 1);
            }
            right++;
        }
        return ret == 0 ? -1 : n - ret;
    }
};
  • 标准版本
cpp 复制代码
class Solution 
{
public:
    int minOperations(vector<int>& nums, int x) 
    {
        int sum = 0;
        for(int a : nums) sum += a;
        int target = sum - x;
        //细节问题
        if(target < 0) return -1;
        int ret = -1;
        for(int left = 0,right = 0,tmp = 0;right < nums.size();right++)
        {
            tmp += nums[right];//进窗口
            while(tmp > target)//判断
                tmp -= nums[left++];//出窗口
            if(tmp == target)//更新结果
                ret = max(ret,right - left + 1);
        }
        if(ret == -1) return ret;
        else return nums.size() - ret;
    }
};

试题5:水果成篮

算法原理

转化为找出一个最长的子数组长度,子数组中不超过两种类型的水果

解法一:暴力枚举+哈希表

通过哈希表判断水果种类是否超过两个

解法二:滑动窗口

left = 0,right = 0

进窗口: hash[f[right]]++

判断是否出窗口: 如果hash.size() > 2 ,hash[f[left]]--

更新结果

代码编写

  • 个人版本
cpp 复制代码
class Solution 
{
public:
    int totalFruit(vector<int>& fruits) 
    {
        unordered_map<int,int> hash;
        int left = 0,right = 0;
        int sum = 0;
        int n = fruits.size();
        while(right < n)
        {
            hash[fruits[right]]++;
            while(hash.size() > 2)
            {
                hash[fruits[left]]--;
                if(hash[fruits[left]] == 0)
                {
                    hash.erase(fruits[left]);
                }
                left++; 
            }
            sum = max(sum,right - left + 1);
            right++;
        }
        return sum;
    }
};
  • 标准版本
cpp 复制代码
class Solution 
{
public:
    int totalFruit(vector<int>& f) 
    {
        unordered_map<int,int> hash;//统计窗口内出现了多少种水果
        int ret = 0;
        for(int left = 0,right = 0; right < f.size(); right++)
        {
            hash[f[right]]++;//进窗口
            while(hash.size() > 2)//判断
            {
                //出窗口
                hash[f[left]]--;
                if(hash[f[left]] == 0)
                {
                    hash.erase(f[left]);
                }
                left++;
            }
            ret = max(ret,right - left + 1);
        }
        return ret;
    }
};
  • 优化版本

用数组模拟哈希表

cpp 复制代码
class Solution 
{
public:
    int totalFruit(vector<int>& f) 
    {
        int hash[1000001] = { 0 };//统计窗口内出现了多少种水果
        int ret = 0;
        for(int left = 0,right = 0,kinds = 0; right < f.size(); right++)
        {
            if(hash[f[right]] == 0)//维护水果的种类
            {
                kinds++;
            }
            hash[f[right]]++;//进窗口
            while(kinds > 2)//判断
            {
                //出窗口
                hash[f[left]]--;
                if(hash[f[left]] == 0)
                {
                    kinds--;
                }
                left++;
            }
            ret = max(ret,right - left + 1);
        }
        return ret;
    }
};

试题6:找到字符串中所有字母异位词

算法原理

如何快速判断两个字符串是否是异位词

方法一:排序+比较:N*logN + N

方法二:利用哈希表,统计出现字符的次数

解法一:暴力枚举

解法二:滑动窗口+哈希表

left = 0,right = 0

进窗口:hash2[in]++

判断:right - left + 1 > m

出窗口:hash2[out]--

更新结果:check(hash1,hash2)

时间复杂度:26 * N → O(N)

优化:更新结果的判断条件

利用变量count来统计窗口中有效字符的个数

进窗口:进入后,hash2[in] ≤ hash1[in],count++

出窗口:出去前,hash2[out] ≤ hash1[out],count--

更新结果:count == m

代码编写

  • 个人版本
cpp 复制代码
class Solution 
{
public:
    vector<int> findAnagrams(string s, string p) 
    {
        vector<int> v;
        int hash1[26] = {0};
        int hash2[26] = {0};
        int len = p.size();
        int n = s.size();
        int count = 0;
        for(char ch : p)
        {
            hash1[ch - 'a']++;
        }
        int left = 0,right = 0;
        while(right < n)
        {
            hash2[s[right] - 'a']++;
            if(hash2[s[right] - 'a'] <= hash1[s[right] - 'a'])
            {
                count++;
            }
            if(right - left + 1 > len)
            {
                if(hash2[s[left] - 'a'] <= hash1[s[left] - 'a'])
                {
                    count--;
                }
                hash2[s[left] - 'a']--;
                left++;
            }
            if(count == len)
            {
                v.push_back(left);
            }
            right++;
        }
        return v;
    }
};
  • 标准版本
cpp 复制代码
class Solution 
{
public:
    vector<int> findAnagrams(string s, string p) 
    {
        vector<int> ret;
        int hash1[26] = {0};//统计字符串p中每个字符出现的个数
        for(auto ch : p) hash1[ch - 'a']++;

        int hash2[26] = {0};//统计窗口里面的每一个字符出现的个数
        int m = p.size();
        for(int left = 0,right = 0,count = 0;right < s.size();right++)
        {
            char in = s[right];
            if(++hash2[in - 'a'] <= hash1[in - 'a']) count++;//进窗口 + 维护count
            if(right - left + 1 > m)//判断
            {
                char out = s[left++];
                if(hash2[out - 'a']-- <= hash1[out - 'a']) count--;//出窗口 + 维护count
            }
            //更新结果
            if(count == m) ret.push_back(left);
        }
        return ret;
    }
};

试题7:串联所有单词的字串

算法原理

解法一:滑动窗口+哈希表

哈希表:hash<string,int>

left与right指针的移动:移动的步长是每个单词的长度

滑动窗口执行的次数:单词的长度个数

代码编写

  • 个人版本
cpp 复制代码
class Solution 
{
public:
    vector<int> findSubstring(string s, vector<string>& words) 
    {
        vector<int> ret;
        int len = words[0].size();
        int n = words.size();
        int count = 0;
        unordered_map<string,int> hash1,hash2;
        for(auto e : words)
        {
            hash1[e]++;
        }
        for(int i = 0;i < len;i++)
        {
            int left = i,right = i;
            count = 0;
            hash2.clear();
            while(right + len <= s.size())
            {
                string cur_right = s.substr(right,len);
                hash2[cur_right]++;
                if(hash2[cur_right] <= hash1[cur_right])
                {
                    count++;
                }
                if(right - left + 1 > n * len)
                {
                    string cur_left = s.substr(left,len);
                    if(hash2[cur_left] <= hash1[cur_left])
                    {
                        count--;
                    }
                    hash2[cur_left]--;
                    left += len;
                }
                if(count == n)
                {
                    ret.push_back(left);
                }
                right += len;
            }
        }
        return ret;
    }
};
  • 标准版本
cpp 复制代码
class Solution 
{
public:
    vector<int> findSubstring(string s, vector<string>& words) 
    {
        unordered_map<string,int> hash1;//保存words里面所有单词的频次
        for(auto& s : words) hash1[s]++;
        int len = words[0].size(),m = words.size();
        vector<int> ret;
        for(int i = 0;i < len;i++)//执行len次
        {
            unordered_map<string,int> hash2;//维护窗口内单词的频次
            for(int left = i,right = i,count = 0;right + len <= s.size();right += len)
            {
                //进窗口+维护count
                string in = s.substr(right,len);
                hash2[in]++;
                if(hash2[in] <= hash1[in]) count++;
                //判断
                if(right - left + 1 > len * m)
                {
                    //出窗口+维护count
                    string out = s.substr(left,len);
                    if(hash2[out] <= hash1[out]) count--;
                    hash2[out]--;
                    left += len;
                }
                //更新结果
                if(count == m)
                {
                    ret.push_back(left);
                }
            }
        }
        return ret;
    }
};

试题8:最小覆盖子串

算法原理

解法一:暴力枚举+哈希表

解法二:滑动窗口+哈希表

left = 0,right = 0

进窗口:hash2[in]++

判断:check(hash1,hash2)

更新结果:起始位置、最短长度

出窗口:hash2[out]--,直到判断不成立

优化:判断条件

使用变量count统计有效字符的种类

进窗口:进之后,当hash2[in] == hash1[in],count++

出窗口:出之前,当hash2[out] == hash1[out],count--

判断条件:count == hash1.size()

代码编写

  • 标准版本
cpp 复制代码
class Solution 
{
public:
    string minWindow(string s, string t) 
    {
        int hash1[128] = {0};//统计字符串t中每个字符的频次
        int kinds = 0;//统计有效字符的种类
        for(auto ch : t)
        {
            if(hash1[ch] == 0)
            {
                kinds++;
            }
            hash1[ch]++;
        }
        int hash2[128] = {0};//统计窗口内每个字符的频次
        int minlen = INT_MAX;
        int begin = -1;
        for(int left = 0,right = 0,count = 0;right < s.size();right++)
        {
            //进窗口
            char in = s[right];
            hash2[in]++;
            //维护count
            if(hash2[in] == hash1[in])
            {
                count++;
            }
            //判断条件
            while(count == kinds)
            {
                //更新结果
                if(right - left + 1 < minlen)
                {
                    minlen = right - left + 1;
                    begin = left;
                }
                char out = s[left];
                left++;
                //维护count
                if(hash2[out] == hash1[out])
                {
                    count--;
                }
                //出窗口
                hash2[out]--;
            }
        }
        if(begin == -1)
        {   
            return "";
        }
        else
        {
            return s.substr(begin,minlen);
        }
    }
};
相关推荐
Mr_pyx2 小时前
LeetCode HOT 100 —— 矩阵置零(多种解法详解)
算法·leetcode·矩阵
葫三生2 小时前
《论三生原理》系列:文化自信、知识范式重构与科技自主创新的思想运动源头?
大数据·人工智能·科技·深度学习·算法·重构·transformer
我叫Ycg2 小时前
C++ 中关于插入函数insert() 与 emplace() 的区别与使用建议
开发语言·c++
谭欣辰2 小时前
区间动态规划精解
c++·动态规划
YuanDaima20482 小时前
矩阵基础原理与题目说明
人工智能·笔记·python·学习·线性代数·矩阵
Q741_1472 小时前
每日一题 力扣 3761. 镜像对之间最小绝对距离 哈希表 数组 C++ 题解
c++·算法·leetcode·哈希算法·散列表
南宫萧幕2 小时前
奈奎斯特判据 + MATLAB建模实现 + 车辆纵向动力学 详细推导笔记
笔记·matlab·simulink
John.Lewis2 小时前
C++加餐课-哈希:扩展学习(2)布隆过滤器
c++·算法·哈希算法
网域小星球2 小时前
C++ 从 0 入门(三)|类与对象基础(封装、构造 / 析构函数,面试必考)
开发语言·c++·面试·构造函数·析构函数