【算法】基础算法002之滑动窗口(二)

👀樊梓慕:个人主页

 🎥个人专栏:《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C++》《Linux》《算法》

🌝每一个不曾起舞的日子,都是对生命的辜负


目录

前言

 5.水果成篮(medium)

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

7.串联所有单词的子串(hard) 

8.最小覆盖字串(hard)


前言

滑动窗口专题续作,本篇文章继续围绕滑动窗口进行讲解,并辅以实战OJ题帮助理解。


 欢迎大家📂收藏📂以便未来做题时可以快速找到思路,巧妙的方法可以事半功倍。 

=========================================================================

GITEE相关代码:🌟樊飞 (fanfei_c) - Gitee.com🌟

=========================================================================


 5.水果成篮(medium)

904. 水果成篮 - 力扣(LeetCode)https://leetcode.cn/problems/fruit-into-baskets/description/

你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示,其中 fruits[i] 是第 i 棵树上的水果 种类 。

你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:

  • 你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。
  • 你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
  • 一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。

给你一个整数数组 fruits ,返回你可以收集的水果的 最大 数目。

​ 阅读题目,其实就是找出一个最长的子数组的长度,要求子数组中不能超过两种元素。

思路:

  • 如果大小超过2:说明窗口内水果种类超过了两种。那么就从左侧开始依次将水果划出窗口,直到哈希表的大小小于等于2,然后更新结果;
  • 如果没有超过2:说明当前窗口内水果的种类不超过两种,直接更新结果ret。
     

 有了思路,画图独立完成代码,不要直接看博主的代码。

class Solution {
public:
    int totalFruit(vector<int>& fruits) {
        unordered_map<int, int> hash;
        int left = 0, right = 0;
        int ret = 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++;
            }
            ret = max(ret, right - left + 1);//更新结果
            right++;
        }
        return ret;
    }
};

但如果使用容器,我们需要频繁地erase元素,这就牺牲了一定的时间。

又因为题目说明元素个数是有限的:

​所以我们可以利用数组模拟一个哈希表,这样效率会显著提升。 

class Solution {
public:
    int totalFruit(vector<int>& fruits) {
        int hash[100001]={0};
        int left=0,right=0,kinds=0;
        int ret=0;
        int n=fruits.size();
        while(right<n)
        {
            if(hash[fruits[right]]==0) kinds++;
            hash[fruits[right]]++;//进入窗口
            while(kinds>2)//判断
            {
                hash[fruits[left]]--;//离开窗口
                if(hash[fruits[left]]==0)
                    kinds--;
                left++;
            }
            ret=max(ret,right-left+1);//更新结果
            right++;
        }
        return ret;
    }
};


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

438. 找到字符串中所有字母异位词 - 力扣(LeetCode)https://leetcode.cn/problems/find-all-anagrams-in-a-string/

给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。

不难发现,我们需要在字符串 s 中维护一个滑动窗口,且该滑动窗口的长度始终与字符串 p 相等。

然后依据该窗口内的元素构建哈希表与字符串 p 的哈希表作比较,如果两个哈希表相同,那么就证明滑动窗口内为字符串 p 的异位词。

那么如何比较两个哈希表是否相同呢?如题意:

​字符串 s 和 p 仅包含小写字母,所以我们只要遍历即可,时间复杂度为常数级,可以忽略。 

有了思路,画图独立完成代码,不要直接看博主的代码。 

class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        int hash1[26] = { 0 };
        for (auto e : p)
            hash1[e - 'a']++;
        int hash2[26] = { 0 };
        int left = 0, right = 0;
        int np = p.size();
        int ns = s.size();
        vector<int> ret;
        while (right < ns)
        {
            hash2[s[right] - 'a']++;//进入窗口
            if (right - left + 1 > np)//判断
                hash2[s[left++] - 'a']--;//离开窗口
            int flag = 0;
            for (int i = 0; i < 26; i++)
                if (hash1[i] != hash2[i])
                    flag = 1;
            if (flag == 0)
                ret.push_back(left);//更新结果
            right++;
        }
        return ret;
    }
};

可是如果 s 和 p 内不光存储小写字母,或者 s 和 p 是某种容器存储的是字符串,我们又该如何处理呢?如果还按照遍历的方式显然不现实,所以我们需要引入『 有效字符计数器count』。

  • 在每次『 进入窗口』之后,要维护count的值:如果该进入窗口的字符在 s哈希表 中的数目小于或等于 p哈希表 中的数目,那么就证明此时进入窗口的字符是 有效字符,count++;
  • 在每次『 离开窗口』之前,要维护count的值:如果该离开窗口的字符在 s哈希表 中的数目小于或等于 p哈希表 中的数目,那么就证明要离开窗口的字符是 有效字符,count--;
  • 每轮如果 有效字符数目与 p字符串长度相等,那么就证明此时 s字符串窗口内是 p字符串 的异位词,将left尾插到vector中。
class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        int hash1[26] = { 0 };
        for (auto e : p)
            hash1[e - 'a']++;
        int hash2[26] = { 0 };
        int left = 0, right = 0, count = 0;
        int np = p.size();
        int ns = s.size();
        vector<int> ret;
        while (right < ns)
        {
            char in = s[right];
            if (++hash2[in - 'a'] <= hash1[in - 'a']) count++;//进入窗口后维护count
            if (right - left + 1 > np)//判断
            {
                char out = s[left++];
                if (hash2[out - 'a']-- <= hash1[out - 'a']) count--;//离开窗口前维护count
            }
            if (count == np)
                ret.push_back(left);//更新结果
            right++;
        }
        return ret;
    }
};

7.串联所有单词的子串(hard) 

30. 串联所有单词的子串 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/substring-with-concatenation-of-all-words/

给定一个字符串 s 和一个字符串数组 words words 中所有字符串 长度相同

 s 中的 串联子串 是指一个包含  words 中所有字符串以任意顺序排列连接起来的子串。

  • 例如,如果 words = ["ab","cd","ef"], 那么 "abcdef", "abefcd""cdabef", "cdefab""efabcd", 和 "efcdab" 都是串联子串。 "acdbef" 不是串联子串,因为他不是任何 words 排列的连接。

返回所有串联子串在 s 中的开始索引。你可以以 任意顺序 返回答案。

其实本题就是第6题的升级版,只不过第6题的原子是字符,本题的原子是字符串,因为给定的容器words内存储的字符串都是等长的。

所以整体的思路与第6题是完全相同的,只不过需要处理一些细节。


细节一:

执行一次滑动窗口逻辑不能包括所有情况,因为我们把字符串看作一个原子处理,但是字符串由一个个字符构成,一个字符串内的部分字符可以和另一个字符串的部分字符组成新的字符串,所以我们需要充分考虑所有情况,经过观察发现我们需要执行 字符串原子的长度次len 就能包含所有情况,比如:

这反映到left和right开始的位置。 


细节二:

结束条件应为 right + 原子字符串长度len > 字符串长度 。

因为如果大于,right再往后就够不成原子字符串了。


 细节三:

right 与 left 每次移动 原子字符串长度len,而不是1。


细节四:

『 判断』条件应为 right - left + 1 > 原子字符串长度len *  words中的字符串个数。


有了思路,画图独立完成代码,不要直接看博主的代码。 

class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        unordered_map<string,int> hash1;
        for(auto& e: words)
            hash1[e]++;
        
        int ns=s.size();
        int nw=words.size();
        int len=words[0].size();
        vector<int> ret;
        for(int i=0;i<len;i++)//细节1
        {
            int left=i,right=i,count=0;
            unordered_map<string,int> hash2;//维护窗口内单词的频次
            while(right + len <= ns)//细节2
            {
                //进入窗口+维护count
                string in = s.substr(right,len);
                hash2[in]++;
                if(hash2[in]<=hash1[in]) count++;

                //判断
                if(right-left+1 > len*nw) //细节4
                {
                    //离开窗口+维护count
                    string out=s.substr(left,len);
                    if(hash2[out]<=hash1[out]) count--;
                    hash2[out]--;
                    left+=len;//细节3
                }

                if(count==nw)
                {
                    ret.push_back(left);//更新结果
                }
                right+=len;//细节3
            }
        }
        return ret;
    }
};

到这代码还能进一步优化。


细节五:

维护count时,因为判断语句会被执行,所以如果进入窗口的字符串in在hash1中不存在,那么in这个字符串就会加入到hash1中,这无疑是一种浪费,所以在比较之前,我们可以判断一下in是否在hash1中,如果不在那也就没有比较的必要了,out那块同理。

class Solution {
public:
    vector<int> findSubstring(string s, vector<string>& words) {
        unordered_map<string,int> hash1;
        for(auto& e: words)
            hash1[e]++;
        
        int ns=s.size();
        int nw=words.size();
        int len=words[0].size();
        vector<int> ret;
        for(int i=0;i<len;i++)//细节1
        {
            int left=i,right=i,count=0;
            unordered_map<string,int> hash2;//维护窗口内单词的频次
            while(right + len <= ns)//细节2
            {
                //进入窗口+维护count
                string in = s.substr(right,len);
                hash2[in]++;
                if(hash1.count(in) && hash2[in]<=hash1[in]) count++;//细节5

                //判断
                if(right-left+1 > len*nw) //细节4
                {
                    //离开窗口+维护count
                    string out=s.substr(left,len);
                    if(hash1.count(out) && hash2[out]<=hash1[out]) count--;//细节5
                    hash2[out]--;
                    left+=len;//细节3
                }

                if(count==nw)
                {
                    ret.push_back(left);//更新结果
                }
                right+=len;//细节3
            }
        }
        return ret;
    }
};

8.最小覆盖字串(hard)

76. 最小覆盖子串 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/minimum-window-substring/description/

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。

注意:

  • 对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
  • 如果 s 中存在这样的子串,我们保证它是唯一的答案。

 同样的我们最先想到暴力枚举+哈希表的办法,但我们可以观察得到一定的规律做优化。


第一个问题:

当right右移到满足覆盖的条件时,left左移,right是否需要回退呢?

其实不需要,因为中间的元素我们是知道的,只需要在left左移时判断right是否需要移动即可。

  • 当left右移后,如果窗口内还满足覆盖条件,那么就证明right此时可以不动;
  • 当left右移后,如果窗口内不满足覆盖条件,那么就证明right要右移寻找新的满足条件的字符。

 而且根究上面的进出窗口,我们可以知道出窗口之前,即left右移之前,此时窗口内是满足条件的字符串,所以『 更新结果』要在『 离开窗口』之前完成。

并且如果窗口内一直满足覆盖条件,那么就应该一直出窗口,直到不满足覆盖条件为止,所以这里应该用while。


第二个问题:

如何判断是否满足覆盖条件,我们之前说利用哈希表,但两个哈希表又如何判断相等呢?

之前的题目相信对你有所启发,我们是利用了一个 有效字符计数器count 来判断,在这里我们同样可以利用这个 count,但唯一不同的是,此 count 要统计的是字符种类,而不是字符数,因为题目说了,对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。

所以进入窗口之后维护count的条件,应该是哈希表中对应字符的个数相等,此时才证明恰好覆盖。


 有了思路,画图独立完成代码,不要直接看博主的代码。 

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++;
            hash1[ch]++;
        }

        int left=0,right=0,count=0;
        int ns=s.size();
        int nt=t.size();
        int hash2[128]={0};

        int minLen=INT_MAX,begin=-1;

        while(right<ns)
        {
            //进入窗口+维护count
            char in=s[right];
            if(++hash2[in]==hash1[in]) count++;

            while(count==kinds)//判断
            {
                if(right-left+1 <minLen)//更新结果
                {
                    minLen=right-left+1;
                    begin=left;
                }
                //离开窗口+维护count
                char out=s[left++];
                if(hash2[out]==hash1[out]) count--;
                hash2[out]--;
            }
            right++;
        }
        if(begin==-1) return "";
        else return s.substr(begin,minLen);
    }
};

滑动窗口专题结束,接下来是『 二分算法』


=========================================================================

如果你对该系列文章有兴趣的话,欢迎持续关注博主动态,博主会持续输出优质内容

🍎博主很需要大家的支持,你的支持是我创作的不竭动力🍎

🌟~ 点赞收藏+关注 ~🌟

=========================================================================

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

c++入门学习⑥——友元和运算符重载

目录 简介&#xff1a; 友元&#xff1a; 全局函数做友元 类做友元 成员函数做友元 运算符重载 加号运算符重载 代码示例&#xff1a; 输入输出运算符重载 ⭐cin ⭐cout 代码示例&#xff1a; 分析&#xff1a; 自增运算符重载 代码示例&#xff08;成员函数实现…

Paper - CombFold: Predicting structures of large protein assemblies 推理流程

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/136165853 CombFold 是一种新的组装技术&#xff0c;可以利用 AlphaFold-Multimer 预测的可能的亚复合物的结构&#xff0c;来构建大型蛋白质复合…

【Oracle】玩转Oracle数据库(二):体系结构、存储结构与各类参数

前言 嘿伙计们&#xff01;准备好了吗&#xff1f;今天我要和你们探讨一个酷炫的话题——Oracle数据库&#xff01;&#x1f389; 在这篇博文【Oracle】玩转Oracle数据库&#xff08;二&#xff09;&#xff1a;体系结构、存储结构与各类参数&#xff0c;我们要揭开Oracle数据库…

甲方紧急需求带来封闭式开发,项目负责人如何做好团队共识?

在职场上总会遇到各种类型的甲方金主&#xff0c;项目开展过程中也难免出现多种变更要求。本期小编就结合一位希赛学员的工作经验分享&#xff0c;一起来大家探讨下&#xff1a;面对甲方的紧急需求&#xff0c;项目经理该如何做才能带领团队克服困难&#xff0c;最终促成项目收…

【Prometheus】node-exporter、server、Grafana安装与配置

基于Prometheus和K8S构建智能化告警系统 一、Prometheus对kubernetes的监控二、node-exporter组件安装和配置2.1、node-exporter介绍2.2、安装node-exporter【1】拉取镜像【2】编写yaml文件【3】运行pod【4】获取数据 三、Prometheus server安装和配置3.1、创建sa账号&#xff…

Mysql 权限与安全管理

0 引言 MySQL是一个多用户数据库&#xff0c;具有功能强大的访问控制系统&#xff0c;可以为不同用户指定允许的权限。MySQL用户可以分为普通用户和root用户。root用户是超级管理员&#xff0c;拥有所有权限&#xff0c;包括创建用户、删除用户和修改用户的密码等管理权限&…

单机环境搭建Redis伪集群

1、Redis版本 [rootwsdhla ~]# redis-server -v Redis server v6.2.6 sha00000000:0 mallocjemalloc-5.1.0 bits64 buildbf23dac15dfc00fa[rootwsdhla ~]# redis-cli -v redis-cli 6.2.62、创建节点目录 创建6个节点目录&#xff0c;分别复制一份redis.conf并编辑&#xff1a…

电路设计(20)——数字电子钟的multism仿真

1.设计要求 使用数字芯片&#xff0c;设计一个电子钟&#xff0c;用数码管显示&#xff0c;可以显示星期&#xff0c;时、分、秒&#xff0c;可以有按键校准时间。有整点报警功能。 2.设计电路 设计好的multism电路图如下所示 3.芯片介绍 时基脉冲使用555芯片产生。在仿真里面…

hal/SurfaceFlinger/perfetto实战需求问题探讨作业-千里马framework开发

背景 hi&#xff0c;粉丝朋友们&#xff1a; 在新课halperfettosurfaceflinger https://mp.weixin.qq.com/s/LbVLnu1udqExHVKxd74ILg 推出后&#xff0c;各位学员朋友们都积极响应&#xff0c;开始马不停蹄的学习&#xff0c;学员学习后希望有更多的实战案例或者项目拿来练手&…

自动化上位机开发C#100例:雷赛运动控制卡EtherCAT总线卡C#封装类

自动化上位机开发C#100例:雷赛运动控制卡EtherCAT总线卡C#封装类 文章目录 LTDMC.dll下载LTDMC.cs LTDMC.dll C#调用封装下载ICard.cs 运动控制卡接口Card.cs 运动控制卡抽象类CardLTDMC.cs 雷赛运动控制卡EtherCAT总线卡实现类CardList.cs 总线卡列表封装 LTDMC.dll下载 最新…

OpenAI公布阻止国家相关威胁行为者对人工智能的恶意使用(包括中国、朝鲜、伊朗、俄罗斯)

曾梦想执剑走天涯&#xff0c;我是程序猿【AK】 目录 简述总结 简述 本篇幅公布OpenAI于2月14日公布的”阻止国家相关威胁行为者对人工智能的恶意使用“一文&#xff0c;其中提及到阻止并限制了来自&#xff08;包括中国、朝鲜、伊朗、俄罗斯&#xff09;的一些用户的使用&…

实习日志15

1.大改了一下界面 1.1.识别与验真 1.2.历史记录 2.改了几个bug 2.1.改json格式用JSON.stringify(value,null,2); 2.2.内嵌页面值与原页面值重复 2.3.验真条件判断 if (isVerifyCell.getValue() "不需要") {if (verifyResultCell.getValue() ! "未查验")…

IDEA实现序列化时如何自动生成serialVersionUID

实现步骤&#xff1a;1.安装GenerateSerialVersionUID插件 2.点击idea左上角File -> Settings -> Editor -> Inspections -> 搜索 Serialization issues &#xff0c;找到 Serializable class without ‘serialVersionUID’ ->打上勾&#xff0c;再点击Apply-&…

计算机功能简介:EC, NVMe

一 EC是指Embedded Controller 主要应用于移动计算机系统和嵌入式计算机系统中&#xff0c;为此类计算机提供系统管理功能。EC的主要功能是控制计算机主板上电时序、管理电池充电和放电&#xff0c;提供键盘矩阵接口、智能风扇接口、串口、GPIO、PS/2等常规IO功能&#xff0c;…

MySQL-锁(LOCK)

文章目录 1. 锁是什么&#xff1f;2. 全局锁2.1 相关语法2.2 特点 3. 表级锁3.1 表锁3.1.1 共享读锁&#xff08;S&#xff09;3.1.2 排它写锁&#xff08;X&#xff09; 3.2 元数据锁&#xff08;MDL&#xff09;3.2 意向锁&#xff08;IS、IX&#xff09; 4. 行级锁4.1 行锁 …

【力扣 - 二叉树的中序遍历】

题目描述 给定一个二叉树的根节点 root &#xff0c;返回 它的 中序 遍历 。 提示&#xff1a; 树中节点数目在范围 [0, 100] 内 -100 < Node.val < 100方法一&#xff1a;递归 思路与算法 首先我们需要了解什么是二叉树的中序遍历&#xff1a;按照访问左子树——…

【Linux系统化学习】深入理解文件系统(Ext2文件系统)

目录 前言 磁盘的物理结构 物理结构 磁头和盘片工作解析图 盘面区域划分图&#xff08;俯视盘面图&#xff09; 扇区的寻址、定位&#xff08;CHS定位&#xff09; 磁盘存储的逻辑抽象结构 LBA定址 文件系统 磁盘分区 EXT2文件系统 组块中的信息介绍 查看inode编号…

代码随想录算法训练营|二叉树总结

二叉树的定义&#xff1a; struct TreeNode {int val;TreeNode* left;TreeNode* right;TreeNode():val(0),left(nullptr),right(nullptr){}TreeNode(int val):val(val),left(nullptr),right(nullptr){}TreeNode(int val,TreeNode* left,TreeNode* right):val(val),left(left),…

【Linux】软件包管理器 yum | vim编辑器

前言: 软件包管理器 yum和vim编辑器讲解 文章目录 软件包管理器 yum编辑器-vim四种模式普通模式批量化注释和批量化去注释末行模式临时文件 软件包管理器 yum yum&#xff08;Yellowdog Updater, Modified&#xff09;是一个在基于 RPM&#xff08;管理软件包的格式和工具集合&…

Smart Link和Monitor Link简介

定义 Smart Link&#xff0c;又叫做备份链路。一个Smart Link由两个接口组成&#xff0c;其中一个接口作为另一个的备份。Smart Link常用于双上行组网&#xff0c;提供可靠高效的备份和快速的切换机制。 Monitor Link是一种接口联动方案&#xff0c;它通过监控设备的上行接口…