【LeetCode热题100】滑动窗口

news2024/9/19 19:48:12

这篇博客总结了滑动窗口的8道常见题目,分别是:长度最小的子数组、无重复字符的最长子串、 最大连续1的个数III、将x减到0的最小操作数、水果成篮、找到字符串中所有字母异位词、串联所有单词的子串、最小覆盖子串。

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) 
    {
        int len = INT_MAX,left=0,right=0,sum=0;
        int n = nums.size();
        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;
    }
};

题目分析:这道题有两种解法:暴力解法和优雅解法。首先我们先从暴力解法说起,暴力枚举出所有子数组的和,定义left和right都指向数组第一个元素,求出left和right之间元素的和,然后固定left,right++,sum加上right指向的元素,然后继续right++,sum再加上right指向的元素,当sum>=taregt时,算出这时left和right之间元素个数,然后left继续右移,直到找到结尾,然后left++,继续right从此时的left开始继续往右走,直到找到所有的结果。

但是,暴力解法有很多可改进的区间,首先,当我们找到第一个sum>=taregt时的right,接着right后面的就不用遍历了,肯定不符合条件。然后,当left++后,此时我们不用把right移动到left的位置,只需要让之前的sum减去left上一个位置的值。利用这两点,我们的优雅解法的思路就是,利用单调性,使用“同向双指针”来优化,left和right都只向同一个方向移动,都不回退。同向双指针又称滑动窗口,滑动窗口用来维护区间的和,当两个指针都不回退时,就可以用滑动窗口。

那怎么用滑动窗口呢?1.设置左右窗口left=0和right=0  2.进窗口  3.根据进的窗口判断是否出窗口,循环23步。

滑动窗口的时间复杂度是O(N)。 

 

class Solution {
public:
    int lengthOfLongestSubstring(string s) 
    {
        int hash[128] = {0};
        int ret = 0;
        int n = s.size();
        for(int left = 0,right = 0;right<n;right++)
        {
            hash[s[right]]++;//进窗口
            while(hash[s[right]] == 2)//判断
                hash[s[left++]]--;//出窗口
            ret = max(ret,right-left+1);//更新
        }
        return ret;

    }
};

题目分析:开始时,我们设置left和right都指向字符串的开始,然后right向右走,直到找到重复的字符串,然后让left++,一直跳过重复的字符串(使用哈希表判断有无重复字符),此时,我们还需要让right退回和left一样的位置重新遍历吗?不需要,因为left和right中间的肯定没有重复字符串,也就是说,在遍历的过程中,left和right都不需要回退,那么我们就可以使用滑动窗口的思想解决:

1.left=0,right=0

2.进窗口

3.判断

        出窗口

4.更新

class Solution {
public:
    int longestOnes(vector<int>& nums, int k) 
    {
        int cnt = 0;
        int size = nums.size();
        int len = 0;
        for(int left = 0,right = 0;right < size; right++)
        {
            if(nums[right] == 0)//进窗口
            {
                cnt++;
            }
            while(cnt > k)//判断
            {
                if(nums[left] == 0)
                    cnt--;
                left++;//出窗口
            }
            len = max(len,right-left+1);
        }

        return len;

    }
};

题目分析:题目可以转化为找出最长的子数组,其0的个数不超过k个,题目也是有两种解法:暴力解法和优雅解法。

暴力解法:暴力枚举每一个子数组,同时加上zero计数器(一个变量),通过判断zero的大小来判断这是不是符合要求的子数组。

优雅解法:left和right指向数组的开始,right向后遍历,当子数组中的0的个数为k+1时,left++,直到其间子数组0的个数为k,然后right继续向后++,直到最后。我们发现right和left都不需要回退,因此可以使用滑动窗口来解决。1.left = 0,right=0;2.进窗口 3.判断-出窗口-更新结果。

class Solution {
public:
    int minOperations(vector<int>& nums, int x) 
    {
        long long s = 0;
        for(auto e:nums) s+=e;
        int target = s-x;
        if(target < 0) return -1;
        else if(target == 0) return nums.size();
        int len = 0;
        int n = nums.size();
        int sum = 0;
        for(int left = 0,right = 0;right<n;right++)
        {
            //1.进窗口
            sum += nums[right];
            //2.判断
            while(sum > target)
            {
                //出窗口
                sum -= nums[left++];
            }
            if(sum == target)
            {
                len = max(len,right-left+1);
            }
                
        }
        return len == 0?-1:n-len;
    }
};

题目分析:这道题可以转化为找出最长的子数组的长度,所有元素的和正好等于sum-x。经过前面几道题的学习,我们可以明显感觉到可以用滑动窗口的思路解决。1.left=0,right=0 2.进窗口,sum+nums[right],3.判断,是否窗口之间元素的和大于sum-x,如果是,出窗口,直到和≤sum-x,然后出循环后,继续判断是否和等于sum-x,然后依据情况更新len。

class Solution {
public:
    int totalFruit(vector<int>& fruits) 
    {
        //map<int,int> m;
        int hash[100001] = {0};
        int len = 0;
        for(int left = 0,right =0,kinds=0;right<fruits.size();right++)
        {
            if(hash[fruits[right]] == 0) kinds++;
            hash[fruits[right]]++;//进窗口
            while(kinds > 2)//判断
            {
                hash[fruits[left]]--;
                if(hash[fruits[left]] == 0) kinds--;
                left++;
            }
            len = max(len,right-left+1);
        }
        return len;
    }
};

题目分析:题目可以转化为,找出一个最长的子数组的长度,子数组中不超过两种类型的水果。为了判断子数组的水果种类,可以使用哈希表的思想。这道题经过暴力枚举思考后,也可以使用滑动窗口来解决。1.left=0,right=0  2.进窗口,hash[f[right]]++  3.判断,left和right之间水果种类是否超了,如果没有超,更新len,如果超了,出窗口。

class Solution {
public:
    bool check(int* cmp,int* target)
    {
        for(int i = 0;i<26;i++)
        {
            if(cmp[i] != target[i]) return false;
        }
        return true;
    }
    vector<int> findAnagrams(string s, string p) 
    {
        int hash_target[26] =  {0};
        int hash_cmp[26] = {0};
        vector<int> ret;
        int count = 0;//窗口区间有效字符个数
        for(auto e:p)
        {
            hash_target[e-'a']++;
        }
        for(int left = 0,right=0;right <s.size();right++)
        {
            hash_cmp[s[right]-'a']++;//进窗口
            if(right - left + 1 > p.size())//判断
            {
                hash_cmp[s[left++]-'a']--;//出窗口
            }
            if(check(hash_cmp,hash_target)) ret.push_back(left);//更新结果
        }
        return ret;
    }
};

题目解析:首先我们先来想一想,给出两个字符串,如何判断它们是不是变位词,其实,只要这两个字符串中每种字符的个数一样即可。因此,我们可以创建两个哈希表,遍历这两个字符串,分别将这两个字符串中的字符放到哈希表中,最后遍历这两个哈希表,判断这两个哈希表中对应字符的个数是否相同。

好了,现在,我们需要在字符串s中,依次遍历出长度等于p长度的子串,然后判断这个子串是不是p的变位词,子串的长度我们一直要维护成p的长度,也就是子串长度一直不变,因此,我们可以采用滑动窗口的思想,但是,与之前滑动窗口所不同的是,这道题的滑动窗口长度固定。

1.left=0,right=0  2.进窗口,hash_cmp[in]++  3.判断,如果right-left+1>m,那么就需要出窗口hash_target[out]--  3.更新结果,检查这两个字符串的哈希表是否构成变位词。

在上面,我们比较这两个子串是否构成变位词,是通过比较两个哈希表,其实,我们还可以通过利用变量count来统计窗口中“有效字符”的个数,具体来说,在进窗口后,如果hash_cmp[in]<=hash_target[in],count++;在进窗口前,如果hash_cmp[out]<=hash_target[out],说明要出窗口的是有效字符,count--;然后再判断count==m,如果成立,说明窗口内就是p的变位词,更新结果。代码如下:

class Solution {
public:
    vector<int> findAnagrams(string s, string p) 
    {
        int hash_target[26] =  {0};
        int hash_cmp[26] = {0};
        vector<int> ret;
        int count = 0;//窗口区间有效字符个数
        for(auto e:p)
        {
            hash_target[e-'a']++;
        }
        for(int left = 0,right=0;right <s.size();right++)
        {
            hash_cmp[s[right]-'a']++;//进窗口
            if(hash_cmp[s[right]-'a'] <= hash_target[s[right]-'a']) count++;
            if(right - left + 1 > p.size())//判断
            {
                if(hash_cmp[s[left]-'a'] <= hash_target[s[left] - 'a'])count--;
                hash_cmp[s[left++]-'a']--;//出窗口
            }
            if(count == p.size()) ret.push_back(left);//更新结果
        }
        return ret;
    }
};

class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) 
    {
        unordered_map<string,int> hash_target;
        int size = words[0].size();
        vector<int> ret;
        for(auto& e:words)
        {
            hash_target[e]++;
        }
        for(int i = 0;i < size;i++)//执行size次滑动窗口
        {
            unordered_map<string,int> hash_cmp;//需要定义在这里,每次循环就是新的hash_cmp
            for(int left = i,right = i,count=0;right + size <=s.size();right+=size)
            {
                string in(string(s,right,size));
                hash_cmp[in]++;//进窗口
                if(hash_target.count(in) && hash_cmp[in] <= hash_target[in]) count++;
                if(((right - left)/size+1) > words.size())//判断
                {
                    string out(string(s,left,size));
                    //第一个条件判断如果out不存在,那么就不会执行后面的,就不会把out插入
                    if(hash_target.count(out) && hash_cmp[out] <= hash_target[out]) count--;
                    hash_cmp[out]--;
                    left+=size;
                }
                if(count == words.size()) ret.push_back(left);
            }
        }
        return ret;
    }
};

题目解析:这道题和上面的一道题很类似,我们只需要把s中的几个字符看成一个,和上题不同的是:1.我们需要使用map<string,int>这样的容器,来统计区间所包含的字符串 2.left和right每次移动的步数是字符串的长度 3.除了让left=0和right=0,开始遍历外,还需要依次从left=1和right=1、left=2和right=2遍历(假设每个字符串长度为3)。

class Solution {
public:
    string minWindow(string s, string t) 
    {
        int hash_target[128];
        int hash_cmp[128];
        int kinds = 0;//统计有效字符有多少种
        size_t len = INT_MAX;
        size_t begin = 0;
        
        for(auto e:t)
        {
            if(hash_target[e] == 0)
            {
                kinds++;
            }
            hash_target[e]++;
        }
        for(int left=0,right=0,count=0;right < s.size();right++)
        {
            hash_cmp[s[right]]++;//进窗口
            if(hash_cmp[s[right]] == hash_target[s[right]]) count++;
            while(count == kinds)//判断
            {
                if(right-left+1 < len) //更新
                {
                    len = right - left +1;
                    begin = left;
                }
                if(hash_cmp[s[left]] == hash_target[s[left]]) count--;
                hash_cmp[s[left++]]--;//出窗口
            }
        }
        return len==INT_MAX?string(""):s.substr(begin,len);

    }
};

题目分析:在经过暴力枚举分析后,我们发现可以使用暴力枚举和哈希表的方式解决。把t中每个字符依次放到哈希表中得到hash_target,然后暴力枚举s中的子字符串,将子字符串依次放到hash_cmp中,比较这两个哈希表,如果hash_target中每个字符的数量<=hash_cmp中对应的每个字符的数量,就认为这个子字符串符合要求。在经过暴力枚举分析后,我们发现其实也可以用滑动窗口的思想解决,也是需要哈希表配合!步骤:1.left=0,right=0 2.进窗口,hash_cmp[in]++ 3.判断,看当前窗口是否符合要求,如果不符合,继续步骤2,如果符合,则出窗口。

然而,上面比较哈希表的开销比较大,我们再来想一种更优秀的办法来判断当前区间是否符合要求,定义count变量,用于标记有效字符的种类,count的使用方法是,在进窗口之后,当hash_cmp[in]==hash_target[in]时,说明这个字符的要求已经达到,有效字符种类count+1;在出窗口之后,当hash_cmp[out]==hash_target[out]时,说明在出完这个字符后,这个字符就不符合要求了,有效字符种类count-1,这样我们就可以通过判断count和t中字符种类是否相等来确定当前区间是否符合要求。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2055058.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

解决Vue3+Ts打包项目时会生成很多的map文件

正常打包会生成.js和.map文件 怎么去解决它呢&#xff1f; 正常来说我们会在vite.config.ts配置我们的项目打包方式&#xff0c;如下&#xff1a;&#xff08;我这里的target&#xff1a;es2022是为了支持模块中顶层await的使用&#xff09; // Vite 配置文件 export default…

海思NVR源码方案:集成ONVIF、GUI、存储与告警的全功能解决方案

海思平台作为中国领先的半导体厂商之一&#xff0c;其3520D芯片凭借高性能、低功耗和广泛的应用性成为了NVR&#xff08;网络硬盘录像机&#xff09;解决方案的核心选择。海思平台的NVR方案不仅支持多种编码格式&#xff0c;且兼容多种视频监控协议&#xff0c;特别是在ONVIF&a…

NC 二叉搜索树的第k个节点

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 描述 给定一棵结点…

【python】调用openAI api接口批量处理excel中的文本

调用openAI api接口批量处理文本 主页&#xff1a;github; BLOG&#xff1a;BLOG; 教程&#xff1a;视频 1. project简介 &#xff08;1&#xff09;概况 用于在python中调用open AI的API&#xff0c;处理xlsx表格中的自然语言文本。一个专门做dirty work的好帮手 &am…

Linux系统-系统信息网络目录文件的相关命令

1.系统信息和性能查看 查看磁盘的占用情况&#xff1a; df -Th 这是参数连着写。相当于df -T -h df -Th 此命令主要用于监控服务器的磁盘空间&#xff0c;如果空间不够用了&#xff0c;会导致服务器和应用的性能严重下降。这时候要手动清理一些不用的垃圾文件&#xff0c;比…

el-image-pro点击文本也能预览图片,支持下载

背景 element-ui&#xff1a;2.15.14 el-image的预览是没有下载功能的&#xff0c;默认是这样的 且默认是通过点击图片才能预览的&#xff0c;有时候我们显示的是图片名称&#xff0c;那么能不能直接点击图片名称来预览呢&#xff1f; 现在想在预览的时候&#xff0c;给它加一…

探秘陆生生态秘境:eDNA视角下的多营养级物种世界

现今的生物多样性和气候危机迫使我们开发更有效的陆地生态系统监测工具&#xff0c;eDNA宏条形码技术&#xff08;eDNA metabarcoding&#xff09;&#xff0c;能够非侵入性地调查许多生态系统的物种丰富度&#xff0c;不会对生态环境造成干扰。通过分析这些信息&#xff0c;我…

树莓派开发笔记06-树莓派的SPI控制(点亮0.96OLED)

实验说明 我们这里会使用SPI去驱动一个0.96的OLED&#xff0c;首先需要打开SPI sudo raspi-config Interfacing Options------>SPI------>Yes------->OK------->finsh然后将屏幕接到树莓派上&#xff0c;接mosi和sclk的脚&#xff0c;DC接28&#xff0c;RST接29&…

C语言 ——— 学习并使用malloc和free函数

目录 malloc函数的功能 学习malloc函数​编辑 使用malloc函数 free函数的功能 学习并使用free函数​编辑 malloc动态开辟10个整型空间后赋值为0-9&#xff0c;再打印&#xff0c;打印后free malloc函数的功能 malloc函数能向内存申请一块连续可用的空间&#xff0c;并返…

模拟信号-放大器

放大器 放大器的输出信号是直流源和信号源经过放大器后&#xff0c;共同的作用&#xff0c;缺一不可。 直流参数 与放大器中电压源部分有关的参数&#xff0c;即放大器的直流参数。 采用直流电压源供电&#xff0c;电源电压是恒定的&#xff0c;但是电压源的输出电流是变化…

什么是OV SSL证书?如何申请

什么是OVSSL证书 OVSSL证书&#xff0c;全称是Organization Validation SSL Certificate&#xff0c;即组织验证型SSL证书。这是一种高级的SSL证书类型&#xff0c;用于保护网站和应用程序的安全性&#xff0c;特别是在电子商务和企业级网站中广泛应用。OVSSL证书不仅加密网站…

Linux:进程替换

什么是进程替换&#xff1f; 我们的可执行程序&#xff0c;在运行起来的时候就上一个进程 一个进程就会有他的内核数据结构代码和数据 把一个已经成型的进程的代码和数据替换掉&#xff0c;这就叫进程替换 也就是可以通过系统调用把当前进程替换位我们需要的进程 那么替换…

正点原子linux开发板 qt程序交叉编译执行

1.开发板光盘 A-基础资料->5、开发工具->1、交叉编译器->fsl-imx-x11-glibc-x86_64-meta-toolchain-qt5-cortexa7hf-neon-toolchain-4.1.15-2.1.0.sh 拷贝到 Ubuntu 虚拟机 用文件传输系统或者共享文件夹传输到linux虚拟机 用ls -l查看权限&#xff0c;如果是白色的使…

【银河麒麟高级服务器操作系统】实际案例分析,xfsaild占用过高

了解银河麒麟操作系统更多全新产品&#xff0c;请点击访问麒麟软件产品专区&#xff1a;https://product.kylinos.cn 服务器环境及配置 物理机/虚拟机 物理机 处理器&#xff1a; Intel(R) Xeon(R) Silver 4110 CPU 2.10GHz 内存&#xff1a; 65536 MiB (64 GiB) 主板…

HTML5简洁的通用网站模板源码

文章目录 1.设计来源1.1 主界面1.2 模板页面11.3 模板页面21.4 模板页面31.5 模板页面41.6 模板页面5 2.效果和源码2.1 动态效果2.2 源码目录2.3 源代码 源码下载 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weixin_43151418/article/details/1413235…

【AD24报错】GND contains IO Pin and Power Pin objects 的解决方案

【AD24报错】GND contains IO Pin and Power Pin objects 的解决方案 在原理图设计过程中&#xff0c;在元器件接线引脚位置出现红色波浪线&#xff0c;显示错误GND contains IO Pin and Power Pin objects。 点击错误信息可以直接跳转到相关位置。 经查明&#xff0c;是在元…

【FPGA数字信号处理】- 数字信号处理如何入门?

​数字信号处理&#xff08;Digital Signal Processing&#xff0c;简称DSP&#xff09;是一种利用计算机或专用数字硬件对信号进行处理的技术&#xff0c;在通信、音频、视频、雷达等领域发挥着越来越重要的作用&#xff0c;也是FPGA主要应用领域之一。 本文将详细介绍数字信…

高德地图数据采集器|高德地图数据采集软件_一键导出表格

南斗地图数据采集是一款专业采集百度地图、360地图、高德地图、腾讯地图、必应、google、公司、店铺的手机、座机、地址、坐标等数据信息的软件&#xff0c;它与同类软件相比最显著特点是采集地图更专业、采集速度更快、采集更精准、操作方法更简单。 可以导出地图搜索结果数据…

运维学习————Linux环境下Tomcat的部署

目录 一、环境准备 二、 启动测试 三、访问端口修改 四、部署web项目 1、材料准备 2、部署 2.1、上传war包到webapps目录下 2.2、修改项目配置 2.3、浏览器访问 引申 一、环境准备 tomcat安装包&#xff1a;apache-tomcat-9.0.52 JDK环境&#xff1a; 我使用的y…

【TCP】连接管理:三次握手和四次挥手

连接管理 建立连接&#xff1a;三次握手断开连接&#xff1a;四次挥手 网络中的握手/挥手&#xff0c;就是发送不携带业务数据&#xff08;没有载荷&#xff0c;只有报头&#xff09;的数据包&#xff0c;但是能起到“打招呼”这样的效果。次数就是指网络通信的次数。 建立连…