leetCode 76. 最小覆盖子串 + 滑动窗口 + Hash + 图解(详细)

news2024/11/15 8:49:22

76. 最小覆盖子串 - 力扣(LeetCode)


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

注意:

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

示例 1:

输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
解释:最小覆盖子串 "BANC" 包含来自字符串 t 的 'A'、'B' 和 'C'

示例 2:

输入:s = "a", t = "a"
输出:"a"
解释:整个字符串 s 是最小覆盖子串

示例 3:

输入: s = "a", t = "aa"
输出: ""
解释: t 中两个字符 'a' 均应包含在 s 的子串中,因此没有符合条件的子字符串,返回空字符串

滑动窗口知识(这些文字来自 笨猪爆破组,详细文章链接在下文)

(1)窗口的扩张

  • 扩张窗口是为了纳入目标字符,右指针右移,先找到可行解——纳入了所有目标字符。
  • 在还没找齐目标字符之前,左指针不动。因为如果此时它右移,可能丢失现有的目标字符。
  • 什么时候停止扩张窗口?——当前窗口包含了所有目标字符。
  • 此时再纳入字符,条件依然满足,但徒增子串长度。此时应该优化可行解:收窄窗口,左指针右移。

(2)窗口的收缩

  • 保持条件满足的情况下,收缩窗口是优化可行解。当窗口不再包含所有目标字符,即有目标字符丢失,就不再收缩。
  • 此时应该扩张窗口,补充目标字符。
  • 可见,为了找到最优解,一直做两种操作之一,直到窗口的右端到达边界。

(3)滑动窗口的套路

  • 先找到一个可行解,再优化这个可行解。
  • 优化到不能优化,产生出一个可能的最优解。
  • 继续找新的可行解,再优化这个可行解。 ……
  • 在所有可能的最优解中,比较出最优解

作者:笨猪爆破组
链接:https://leetcode.cn/problems/minimum-window-substring/solutions/2500882/leetcode-76-zui-xiao-fu-gai-zi-chuan-hua-ul9n/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 

从左->到下->到右->到下看以下图 

 

(1)思路1:用一个哈希map

class Solution {
public:
    bool check(unordered_map<char,int> mp) {
        for(auto it:mp){
            if(it.second > 0) return false;
	    }
        return true;
    }
    string minWindow(string s, string t) {
        unordered_map<char,int> need;
        int strStart=0,windowLen=INT_MAX;
        int left=0,right=0;
        for(char c:t) {
            need[c]+=1;
        }
        while(right < s.size()) {
            char curChar = s[right];
            if(need.find(curChar)!=need.end()) need[curChar]--;
            while(check(need)) {
                // 更新窗口的长度和起始位置
                int curWindowLen = right-left+1;
                if(curWindowLen < windowLen) {
                    windowLen = curWindowLen;//更新窗口的长度
                    strStart=left;//更新窗口的起始位置
                }
                //继续缩小窗口
                char leftChar = s[left];
                if(need.find(leftChar)!=need.end()) need[leftChar]++;
                left++;
            }
            right++;
        }
        if(windowLen != INT_MAX) return s.substr(strStart,windowLen);
        return "";
    }
};

(2)思路2:实际上还可以把哈希map换成数组,原理其实都是一样的,来看下代码

class Solution {
public:
    string minWindow(string s, string t) {
        vector<int>need(128);
        for(char c:t) {
            need[c]+=1;
        }
        int charCount = t.size();
        int left=0,right=0;
        int strStart=0,windowLen=INT_MAX;
        while(right < s.size()) {
            char curChar = s[right];
            if(need[curChar] > 0) {
                need[curChar]--;
                charCount--;
            }else need[curChar]--;
            while(charCount==0) {
                // 更新窗口的长度和起始位置
                int curWindowLen = right-left+1;
                if(curWindowLen < windowLen) {
                    windowLen = curWindowLen;//更新窗口的长度
                    strStart=left;//更新窗口的起始位置
                }
                //继续缩小窗口
                char leftChar = s[left];
                if(need[leftChar] == 0) {
                    need[leftChar]++;
                    charCount++;
                }else need[leftChar]++;
                left++;
            }
            right++;
        }
        if(windowLen != INT_MAX) return s.substr(strStart,windowLen);
        return "";
    }
};

 我们可以简化一下代码:

if(need[curChar] > 0) {
    need[curChar]--;
    charCount--;
}else need[curChar]--;

可以写成
if(need[curChar]-- > 0) {
    charCount--;
}
if(need[leftChar] == 0) {
    need[leftChar]++;
    charCount++;
}else need[leftChar]++;

可以写成
if(need[leftChar]++ == 0) charCount++;

(2.1)于是,就有如下代码:

class Solution {
public:
    string minWindow(string s, string t) {
        vector<int>need(128);
        for(char c:t) {
            need[c]+=1;
        }
        int charCount = t.size();
        int left=0,right=0;
        int strStart=0,windowLen=INT_MAX;
        while(right < s.size()) {
            char curChar = s[right];
            if(need[curChar]-- > 0) {
                charCount--;
            }
            while(charCount==0) {
                // 更新窗口的长度和起始位置
                int curWindowLen = right-left+1;
                if(curWindowLen < windowLen) {
                    windowLen = curWindowLen;//更新窗口的长度
                    strStart=left;//更新窗口的起始位置
                }
                //继续缩小窗口
                char leftChar = s[left];
                if(need[leftChar]++ == 0) charCount++;
                left++;
            }
            right++;
        }
        if(windowLen != INT_MAX) return s.substr(strStart,windowLen);
        return "";
    }
};

很多题解会写成这样,好处是减少一步加一操作。但是看的时候可能会有点懵

① 思路1的改进:

while(right < s.size()) {
    char curChar = s[right];
    if(need.find(curChar)!=need.end()) need[curChar]--;
    while(check(need)) {
        // 更新窗口的长度和起始位置
        int curWindowLen = right-left+1;
        if(curWindowLen < windowLen) {
            windowLen = curWindowLen;//更新窗口的长度
            strStart=left;//更新窗口的起始位置
        }
        //继续缩小窗口
        ...
        ...
        ...
    }
    right++;
}

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

while(right < s.size()) {
    char curChar = s[right];
    if(need.find(curChar)!=need.end()) need[curChar]--;
    right++;
    while(check(need)) {
        // 更新窗口的长度和起始位置
        if(right-left< windowLen) {
            windowLen = right-left;//更新窗口的长度
            strStart=left;//更新窗口的起始位置
        }
        //继续缩小窗口
        ...
        ...
        ...
    }
}

 ② 思路2的改进

while(right < s.size()) {
    char curChar = s[right];
    if(need[curChar]-- > 0) {
        charCount--;
    }
    while(charCount==0) {
        // 更新窗口的长度和起始位置
        int curWindowLen = right-left+1;
        if(curWindowLen < windowLen) {
            windowLen = curWindowLen;//更新窗口的长度
            strStart=left;//更新窗口的起始位置
        }
        //继续缩小窗口
        ...
        ...
        ...
    }
    right++;
}

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

while(right < s.size()) {
    if(need[s[right++]]-- > 0) {
        charCount--;
    }
    while(charCount==0) {
        // 更新窗口的长度和起始位置
        if(right-left< windowLen) {
            windowLen = right-left;//更新窗口的长度
            strStart=left;//更新窗口的起始位置
        }
        //继续缩小窗口
        ...
        ...
        ...
    }
}

(3) 用两个哈希map

class Solution {
public:
    string minWindow(string s, string t) {
        unordered_map<char,int> need;
        unordered_map<char,int> window;
        for(auto c:t) {
            need[c]+=1;
        }
        int right=0,left=0;
        int valid=0;
        int start=0,minLen=INT_MAX;
        while(right < s.size()) {
            char cur = s[right];
            // 进行窗口数据一系列更新
            if(need.find(cur)!=need.end()) {
                window[cur]++;
                if(window[cur] == need[cur]) valid++;
            }
            while(need.size() == valid) {
                if(right - left + 1 < minLen) {
                    start = left;
                    minLen = right - left + 1;
                }
                // d 是将移除窗口的字符串
                char deleteChar = s[left];
                // 进行窗口内数据当一系列更新
                if(window.find(deleteChar)!=window.end()) {
                    if(window[deleteChar] == need[deleteChar]) valid--;
                    window[deleteChar]--;
                }
                // 左边移动窗口
                left++;
            }
            right++;
        }
        return minLen == INT_MAX ? "" : s.substr(start,minLen);
    }
};

推荐和参考文章:76. 最小覆盖子串 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/minimum-window-substring/solutions/736507/shu-ju-jie-gou-he-suan-fa-hua-dong-chuan-p6ip/

76. 最小覆盖子串 - 力扣(LeetCode)icon-default.png?t=N7T8https://leetcode.cn/problems/minimum-window-substring/solutions/257928/yi-bu-bu-xing-cheng-hua-dong-chuang-kou-si-lu-shen/ 此题的另一种思路,参考笨猪爆破组写的C++版本,感兴趣的友友可以移步看一下喔!

leetCode 76. 最小覆盖子串 + 滑动窗口 + 哈希Hash-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/weixin_41987016/article/details/134088989?spm=1001.2014.3001.5501

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

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

相关文章

分布估计算法(Estimation of distribution algorithm,EDA)

概论 分布估计算法&#xff08;Estimation of distribution algorithm&#xff0c;EDA&#xff09;是一种新兴的基于统计学原理的随机优化算法。 为什么要叫这个名字呢&#xff1f; 首先&#xff0c;“分布”指的就是概率分布。 其次&#xff0c;“估计”指的是这个概率分布…

2023年【河北省安全员B证】新版试题及河北省安全员B证试题及解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 河北省安全员B证新版试题根据新河北省安全员B证考试大纲要求&#xff0c;安全生产模拟考试一点通将河北省安全员B证模拟考试试题进行汇编&#xff0c;组成一套河北省安全员B证全真模拟考试试题&#xff0c;学员可通过…

C++STL----list的模拟实现

文章目录 list模拟实现的大致框架节点类的模拟实现迭代器类的模拟实现迭代器类存在的意义迭代器类的模板参数说明运算符的重载--运算符的重载&#xff01;与运算符的重载*运算符的重载->运算符的重载 list的模拟实现默认成员函数迭代器相关函数元素修改相关函数front和backi…

“KeyarchOS:国产Linux新星的崛起与创新之路“

简介 KOS&#xff0c;也就是KeyarchOS&#xff0c;是一款由国内团队开发的服务器操作系统。它因为几个特点而受到我的青睐和一些用户的关注。 首先&#xff0c;KOS注重安全性和稳定性。它有一些防护和隔离功能&#xff0c;来帮助系统稳定运行&#xff0c;而且是中文语言更接地…

从零开始的LINUX(三)

bc&#xff1a;进行浮点数运算 uname&#xff1a;查看当前的操作系统 ctrlc&#xff1a;中止当前正在执行的程序 ctrld&#xff1a;退出xshell shutdown&#xff1a;关机 reboot&#xff1a;重启 shell外壳&#xff1a; 作用&#xff1a;1、命令解释&#xff08;将输入的程序…

QSS 自定义QLineEdit

QSS 自定义QLineEdit Chapter1 QSS 自定义QLineEdit简述常用属性和伪状态效果图QSS源码参考 Chapter1 QSS 自定义QLineEdit 原文链接&#xff1a;https://blog.csdn.net/Staranywhere/article/details/107306276 简述 本文将通过简单示例介绍QLineEdit样式如何自定义。 常用…

Linux高性能服务器编程——ch9笔记

第9章 I/O复用 同时监听多个文件描述符&#xff0c;但本身是阻塞的。 9.1 select系统调用 在一段指定时间内&#xff0c;监听用户感兴趣的文件描述符上的可读、可写和异常等事件是否就绪。 :::tips int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* except…

掌握 JavaScript:从初学者到高级开发者的完整指南之JavaScript对象(二)

JavaScript基础知识 1. JavaScript对象1.1.1 基本对象1.1.1.1 Array对象语法格式特点属性和方法 1.1.1.2 String对象语法格式属性和方法 1.1.1.3 JSON对象自定义对象json对象 1. JavaScript对象 可以大体分页3大类&#xff1a; 第一类&#xff1a;基本对象,我们主要学习Array…

Python---小海龟会画画---利用turtle(海龟)模块

1、小海龟模块 在Python3版本中&#xff0c;新增加了一个模块叫做turtle&#xff08;海龟&#xff09;&#xff0c;专门用于绘制图形图像 2、模块如何使用 ① 导入模块 import turtle② 使用turtle模块中已经定义好的方法 turtle.forward(数值) # 从左向右&#xff0c;绘制一…

docker 部署 若依 Ruoyi springboot+vue分离版 dockerCompose

本篇从已有虚拟机/服务器 安装好dokcer为基础开始讲解 1.部署mysql 创建conf data init三个文件夹 conf目录存放在mysql配置文件 init目录存放着若依数据库sql文件&#xff08;从navicat导出的并非若依框架自带sql&#xff09; 创建一个属于本次若依部署的网段&#xff08;只…

python:使用Scikit-image对遥感影像进行梯度特征提取(gradient)

作者:CSDN @ _养乐多_ 在本博客中,我们将介绍如何使用Scikit-Image来进行梯度特征提取(gradient),并且提供一个示例代码,演示了如何在单波段遥感图像上应用这些方法。 梯度特征是指用于表示图像中亮度或颜色变化的特征。它包括两个关键成分:梯度幅值和梯度方向。梯度幅…

吴恩达《机器学习》1-3:监督学习

一、监督学习 例如房屋价格的数据集。在监督学习中&#xff0c;我们将已知的房价作为"正确答案"&#xff0c;并将这些价格与房屋的特征数据一起提供给学习算法。学习算法使用这些已知答案的数据来学习模式和关系&#xff0c;以便在未知情况下预测其他房屋的价格。这就…

底层驱动day8作业

代码&#xff1a; //驱动程序 #include<linux/init.h> #include<linux/module.h> #include<linux/of.h> #include<linux/of_gpio.h> #include<linux/gpio.h> #include<linux/timer.h>struct device_node *dnode; //unsigned int gpiono; …

内存管理:TLSF算法

动态内存分配DSA DSA&#xff1a;Dynamic Storage Allocation&#xff0c;用于动态管理程序运行时所需的内存。动态内存分配涉及在程序运行时根据需要分配和释放内存&#xff0c;以存储数据结构和数据。 内存管理方式&#xff1a;动态内存分配与静态内存分配相对应&#xff0…

json格式存储b64编码的rgb raw数据

1.rgb raw数据准备 利用python将jpg里面的rgb raw数据提取出来。 import cv2# 读取 JPG 图像 image_path 1.jpg image cv2.imread(image_path)#imread读出来的顺序是BGR print("image shape:",image.shape)# 将图像由BGR转换为 RGB 数据 rgb_data cv2.cvtColor(im…

什么是水坑攻击

水坑攻击 1. 水坑攻击的概念1. 水坑攻击的原理2. 水坑攻击的常用手段3. 典型水坑攻击事件 1. 水坑攻击的概念 水坑攻击&#xff08;Watering Hole Attack&#xff09;是一种网络攻击方法&#xff0c;其名称来源于自然界的捕食方式。 攻击者会通过前期的调查或各种社会工程手段…

2023年【河北省安全员B证】免费试题及河北省安全员B证作业考试题库

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 河北省安全员B证免费试题考前必练&#xff01;安全生产模拟考试一点通每个月更新河北省安全员B证作业考试题库题目及答案&#xff01;多做几遍&#xff0c;其实通过河北省安全员B证在线考试很简单。 1、【多选题】一般…

计算机中了faust勒索病毒怎么办,faust勒索病毒解密,数据恢复

近年来网络技术得到了飞速发展&#xff0c;为人们的企业生产生活提供了极大便利&#xff0c;但随之而来的网络安全威胁也不断增加&#xff0c;近期&#xff0c;云天数据恢复中心收到了很多企业的求助&#xff0c;企业的计算机服务器遭到了faust勒索病毒攻击&#xff0c;导致企业…

【java学习—九】内部类(7)

文章目录 1. 概念2. 内部类特性3. 内部类实现多重继承的应用 1. 概念 &#xff08;1&#xff09;在 Java 中&#xff0c;允许一个类的定义位于另一个类的内部&#xff0c;前者称为内部类&#xff0c;后者称为外部类。     &#xff08;2&#xff09;Inner class 一般用在定…