力扣经典题目解析--最小覆盖子串

原题地址: . - 力扣(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 的子串中,
因此没有符合条件的子字符串,返回空字符串。

暴力法

暴力法就是遍历所有子串,然后统计子串中每个字符出现的次数,然后跟t中每个字符出现的次数做比较,如果包含t中所有字符并且字符出现的次数大于等于t,则说明该子串符合条件,找出所有满足条件的子串,通过比较得到最小子串

public String minWindow(String s, String t) {
    // 保存自小子串
    String result = "";
    // 统计t中字符串数量
    Map<Character, Integer> tCharStatis = statis(t);
    // 遍历s
    for (int i = 0; i < s.length(); i++) {
        // 遍历子串,子串范围为[i,j)
        for (int j = i + t.length(); j <= s.length(); j++) {
            String subStr = s.substring(i, j);
            Map<Character, Integer> subCharStatis = statis(subStr);
            // 如果子串包含t,则判断是否为最小子串
            if (check(subCharStatis, tCharStatis)) {
                if ("".equals(result) || subStr.length() < result.length()) {
                    result = subStr;
                }
            }
        }
    }
    return result;
}

// 统计字符串中单个字符数量
private Map<Character, Integer> statis(String str) {
    Map<Character, Integer> map = new HashMap<>();
    for (int i = 0; i < str.length(); i++) {
        int count = map.getOrDefault(str.charAt(i), 0);
        count++;
        map.put(str.charAt(i), count);
    }
    return map;
}

// 判断子串是否包含t
private boolean check(Map<Character, Integer> subStatis, Map<Character, Integer> tStatis) {
    for (Character c : tStatis.keySet()) {
        int subCount = subStatis.getOrDefault(c, 0);
        if (subCount < tStatis.get(c)) return false;
    }
    return true;
}

效果:

代码没问题,但是时间复杂度太高,没法满足需求 

滑动窗口

暴力法的缺点是显而易见的:时间复杂度过大,超出了运行时间限制。在哪些方面可以优化呢?

仔细观察可以发现,我们在暴力求解的时候,做了很多无用的比对:对于字符串“ADOBECODEBANC”,当找到一个符合条件的子串“ADOBEC”后,我们会继续仍以“A”作为起点扩展这个子串,得到一个符合条件的“ADOBECO”——它肯定符合条件,也肯定比之前的子串长,这其实是完全不必要的。

代码实现上,我们可以定义两个指针:指向子串“起始点”的左指针,和指向子串“结束点”的右指针。它们一个固定、另一个移动,彼此交替向右移动,就好像开了一个大小可变的窗口、在不停向右滑动一样,所以这就是非常经典的滑动窗口解决问题的应用场景。所以有时候,滑动窗口也可以归类到双指针法。

public String minWindow1(String s, String t) {
    // 保存自小子串
    String result = "";
    // 统计t中字符串数量
    Map<Character, Integer> tCharStatis = statis(t);
    int lp = 0;
    int rp = t.length();
    while (rp <= s.length()) {
        String subStr = s.substring(lp, rp);
        Map<Character, Integer> subCharStatis = statis(subStr);
        // 子串符合条件,则左指针右移
        if (check(subCharStatis, tCharStatis)) {
            if ("".equals(result) || subStr.length() < result.length()) {
                result = subStr;
            }
            lp++;
        } else {
            //不符合条件则右指针右移继续寻找
            rp++;
        }
    }
    return result;
}

// 统计字符串中单个字符数量
private Map<Character, Integer> statis(String str) {
    Map<Character, Integer> map = new HashMap<>();
    for (int i = 0; i < str.length(); i++) {
        int count = map.getOrDefault(str.charAt(i), 0);
        count++;
        map.put(str.charAt(i), count);
    }
    return map;
}

// 判断子串是否包含t
private boolean check(Map<Character, Integer> subStatis, Map<Character, Integer> tStatis) {
    for (Character c : tStatis.keySet()) {
        int subCount = subStatis.getOrDefault(c, 0);
        if (subCount < tStatis.get(c)) return false;
    }
    return true;
}

效果:

比之前有进步,但是还需进一步优化 

滑动窗口优化

我们判断S是否满足包含T中所有字符的时候,调用的方法check其实又是一个暴力法:遍历T中所有字符频次,一一比对。上面的复杂度分析也可以看出,遍历s只用了线性时间,但每次都要遍历一遍T的频次哈希表,这就耗费了大量时间。

我们已经知道,每次指针的移动,只涉及到一个字符的增减。所以我们其实不需要知道完整的频次HashMap,只要获取改变的这个字符的频次,然后再和T中的频次比较,就可以知道新子串是否符合要求了。

public String minWindow(String s, String t) {
    // 保存最小子串
    String result = "";
    // 统计t中字符串数量
    Map<Character, Integer> tCharStatis = statis(t);
    Map<Character, Integer> subCharStatis = new HashMap<>();
    int lp = 0;
    int rp = 1;
    while (rp <= s.length()) {
        char newChar = s.charAt(rp - 1);
        // t中包含该字符再统计
        if (tCharStatis.containsKey(newChar)) {
            int count = subCharStatis.getOrDefault(newChar, 0);
            subCharStatis.put(newChar, count + 1);
        }
        // 子串符合条件,则左指针右移
        while (check(subCharStatis, tCharStatis) && lp < rp) {
            if ("".equals(result) || rp - lp < result.length()) {
                result = s.substring(lp, rp);
            }
            char removedChar = s.charAt(lp);
            if (tCharStatis.containsKey(removedChar)) {
                subCharStatis.put(removedChar, subCharStatis.getOrDefault(removedChar, 0) - 1);
            }
            lp++;
        }
        rp++;
    }
    return result;
}

// 统计字符串中单个字符数量
private Map<Character, Integer> statis(String str) {
    Map<Character, Integer> map = new HashMap<>();
    for (int i = 0; i < str.length(); i++) {
        int count = map.getOrDefault(str.charAt(i), 0);
        count++;
        map.put(str.charAt(i), count);
    }
    return map;
}

// 判断子串是否包含t
private boolean check(Map<Character, Integer> subStatis, Map<Character, Integer> tStatis) {
    for (Character c : tStatis.keySet()) {
        int subCount = subStatis.getOrDefault(c, 0);
        if (subCount < tStatis.get(c)) return false;
    }
    return true;
}

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

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

相关文章

Finetuning Large Language Models: Sharon Zhou

Finetuning Large Language Models 课程地址&#xff1a;https://www.deeplearning.ai/short-courses/finetuning-large-language-models/ 本文是学习笔记。 Goal&#xff1a; Learn the fundamentals of finetuning a large language model (LLM). Understand how finetu…

Vue3:用vite创建Vue3项目

一、简介 vite是新一代前端构建工具&#xff0c;官网地址&#xff1a;https://vitejs.cn vite的优势如下&#xff1a; 轻量快速的热重载&#xff08;HMR&#xff09;&#xff0c;能实现极速的服务启动。对 TypeScript、JSX、CSS 等支持开箱即用。真正的按需编译&#xff0c;不…

【计算机那些事】

目录 【云计算】 【原神用的是UDP还是TCP】 【几个特殊地址】 【socket是什么】 【内网穿透是什么】 【为什么有HTTP协议&#xff0c;还要有websocket协议】 【科普路由器&#xff0c;集线器&#xff0c;交换机&#xff0c;网桥&#xff0c;光猫】 【USB接口那些事】 …

MacOS包管理工具homebrew使用教程

MacOS包管理工具homebrew使用教程 1.概述与安装2.基本使用3.其他常用命令 1.概述与安装 homebrew是Mac OS X上的强大的包管理工具&#xff0c;可以高效管理各种软件包 安装&#xff1a; 1、安装xcode&#xff1a; xcode-select --install2、一行命令下载&#xff1a; /bin…

个人项目介绍3:火车站篇

项目需求&#xff1a; 一比一精确显示火车站主建筑和站台模型。实时响应车辆信息&#xff08;上水&#xff0c;吸污&#xff0c;换乘&#xff09;并同步显示&#xff0c;实时响应车辆进出站信息&#xff0c;并以动画形式模拟。实时响应报警信息&#xff0c;并能在三位中显示&a…

快速搭建Vue前端框架

快速搭建Vue前端框架 安装Vue Vue官方安装过程:https://cli.vuejs.org/zh/guide/installation.html 二.创建Vue工程 2.2 安装淘宝镜像 安装淘宝镜像&#xff08;会让你安装Vue的速度加快&#xff09;&#xff1a; npm config set registry https://registry.npm.taobao.or…

【内推】金山办公 2024届 春季校园招聘

有需要内推的小伙伴吗&#xff1f; 金山办公 各岗位均有 面向应届生春招 QQ群&#xff1a;723529936 内推码&#xff1a;NTASYQI

十秒学会Ubuntu命令行:从入门到进阶

一、引言 在使用Ubuntu操作系统时&#xff0c;命令行界面&#xff08;CLI&#xff09;是不可或缺的一部分。对于初学者来说&#xff0c;掌握基本的命令行操作可以帮助他们更高效地管理系统和软件。本文将介绍一些常见的Ubuntu命令以及如何解决与命令行相关的问题。 二、常用Ubu…

【C语言】内存操作篇---动态内存管理----malloc,realloc,calloc和free的用法【图文详解】

欢迎来CILMY23的博客喔&#xff0c;本篇为【C语言】内存操作篇---动态内存管理----malloc&#xff0c;realloc&#xff0c;calloc和free的用法【图文详解】&#xff0c;感谢观看&#xff0c;支持的可以给个一键三连&#xff0c;点赞关注收藏。 前言 在学完结构体后&#xff08;…

本地搭建xss平台并获取cookie演练

前言 一般而言&#xff0c;搭建xss平台是不被允许的&#xff0c;但是由于教育的目的&#xff0c;搭建xss平台更能让学习者更加直观感受xss漏洞对我们的危害和它的重要性。 搭建xss平台 1.搭建xss平台的基础是在phpstudy一个集成环境上的&#xff0c;所有第一步要安装phpstudy&a…

ardupilot 及PX4姿态误差计算算法对比分析

目录 文章目录 目录摘要1.APM姿态误差计算算法2.PX4姿态误差计算算法3.结论摘要 本节主要记录ardupilot 及PX4姿态误差计算算法差异对比过程,欢迎批评指正。 备注: 1.创作不易,有问题急时反馈 2.需要理解四元物理含义、叉乘及点乘含义、方向余弦矩阵含义、四元数乘法物理含…

Linux环境基础开发工具使用

目录 1.Linux软件包管理器yum 什么是软件包 关于 lrzsz 查看软件包 2.Linux开发工具 2.1.vim的基本概念 2.2vim的基本操作 2.3vim命令模式命令集 1.插入模式 2.从插入模式切换为命令模式 3.移动光标 4.删除文字 5.复制 6.替换 7.撤销上一次的操作 8.更改 2.4v…

Windows10笔记本亮度调节按键失灵

操作&#xff1a;任务管理器 -> 监视器 -> 右键点击 -> 通用即插即用监视器 -> 更新驱动程序 -> 浏览我的电脑以查找我的驱动程序 -> 让我从计算机上的可用驱动程序列表中选取 -> 点击通用即插即用监视器 -> 点击关闭 -> 重启电脑。 第一步&#x…

第三百八十二回

文章目录 1. 概念介绍2. 使用方法3. 代码与效果3.1 示例代码3.2 运行效果 4. 内容总结 我们在上一章回中介绍了"如何修改按钮的形状"相关的内容&#xff0c;本章回中将介绍NavigationBar组件.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在本章…

波奇学Linux:进程通信之命名管道

进程通信的前提&#xff1a;让不同的进程看到同一份文件 匿名管道只能具有血缘关系的进程&#xff0c;毫不相关的进程通信得要命名管道 管道文件不需要刷盘&#xff0c;基于内存级文件 命名管道通过路径文件名确定打开同一个文件&#xff0c;在匿名管道中利用父子进程。 创…

(学习日记)2024.02.29:UCOSIII第二节

写在前面&#xff1a; 由于时间的不足与学习的碎片化&#xff0c;写博客变得有些奢侈。 但是对于记录学习&#xff08;忘了以后能快速复习&#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位&#xff0c;以时间为顺序&#xff0c;仅仅将博客当做一个知识学习的目录&a…

总结:React 中的 state 状态

☝️上文提及&#xff1a;可以通过组件中的重要信息是否由组件自身 state 还是外部 prop 驱动来区分「受控组件」&「非受控组件」。 换言之&#xff0c;props 是对外的&#xff0c;state 是对内的 props&#xff1a;只读&#xff0c;父组件通过 props 传递给子组件其所需要…

AI预测福彩3D第一弹【2024年3月4日预测】

众所周知&#xff0c;深度学习算法&#xff08;AI算法&#xff09;由于其内部含有庞大数量的神经元&#xff0c;理论上能够拟合任意维度的数据&#xff0c;目前在大数据分析领域应用非常广泛&#xff0c;并且能够很好的挖掘数据规律&#xff0c;对相关数据进行预测分析。 前面一…

Tomcat源码解析(二): Bootstrap和Catalina

Tomcat源码系列文章 Tomcat源码解析(一)&#xff1a; Tomcat整体架构 Tomcat源码解析(二)&#xff1a; Bootstrap和Catalina 目录 前言一、启动类Bootstrap1、main2、init3、load与start 二、加载Catalina1、load2、start2.1、注册shutdown钩子2.2、监听shutdown命令2.3、停止…

从零开始学习Netty - 学习笔记 -Netty入门【协议设计和解析】

2.协议设计和解析 协议 在计算机中&#xff0c;协议是指一组规则和约定&#xff0c;用于在不同的计算机系统之间进行通信和数据交换。计算机协议定义了数据传输的格式、顺序、错误检测和纠正方法&#xff0c;以及参与通信的各个实体的角色和责任。计算机协议可以在各种不同的层…