Leetcode 最小覆盖子串

devtools/2024/9/24 18:42:55/

在这里插入图片描述

解题思路:

  1. 哈希表存储字符频率:首先统计字符串 t 中每个字符出现的次数。
  2. 滑动窗口:用两个指针 leftright 来标记当前窗口的左右边界,不断右移 right,直到包含了所有 t 中的字符。然后尝试右移 left,缩小窗口以找到最小覆盖子串。
  3. 更新最小长度:在窗口满足条件时,记录当前窗口的长度并更新最小长度
  4. 时间复杂度:由于左右指针各自只遍历一次字符串,因此时间复杂度为 O(n)。

定义一个变量 count,用来记录当前窗口中还需要匹配多少个字符。初始化时,count 等于 t 的长度。
一旦 count == 0,意味着当前窗口已经包含了 t 中所有字符。此时我们尝试通过右移 left 指针来缩小窗口,并检查最小长度。
HashMap<Character, Integer> 的值是动态变化的。它的值在滑动窗口移动时会随着窗口内字符的增减而调整。具体来说,HashMap 的值记录了当前滑动窗口中对剩余字符需要匹配的次数

Java 代码:

java">class Solution {public String minWindow(String s, String t) {//如果m,n可以取整为0,需要对特殊情况返回空串if(s.length() == 0 || s == null || t.length() == 0 || t == null) {return "";}//然后,首先统计 t 中字符频率HashMap<Character, Integer> TCharfrequency = new HashMap<>();for(char c : t.toCharArray()){TCharfrequency.put(c, TCharfrequency.getOrDefault(c, 0) + 1);}//初始化滑动窗口的左右指针,初始化最小覆盖子串的长度int left = 0;int right = 0;int minLeft = 0;int minLen = Integer.MAX_VALUE;//初始化仍然需要匹配的字符次数 countint count = t.length();//滑动窗口,HashMap 的value值记录了此时滑动窗口中对应key字符剩余需要匹配的次数,是动态变化的while(right < s.length()) {//首先处理当前右指针指向的字符char rChar = s.charAt(right);if(TCharfrequency.containsKey(rChar)) {//如果当前右指针指向的字符在map中出现,说明成功匹配了一个字符,需要减少countif(TCharfrequency.get(rChar) > 0) {//之所以需要判断value大于0,是因为如果value==0,说明当前字符匹配次数达到了,不需要再匹配了,count--;}//同时我们需要更新hashmap中对应的valueTCharfrequency.put(rChar, TCharfrequency.get(rChar) - 1);//为什么需要这行代码?}//处理完当前右指针字符后滑动窗口, 移动右指针right++; //每当滑动窗口之后, 立马进行优化缩小窗口尝试while(count == 0) { //如果count为0,说明当前窗口已经包含了所有t中的字符// 如果使用 if,只能尝试缩小一次,也就是说只能缩小一个字符的范围。// 使用 while 可以确保我们在当前窗口仍然满足所有条件的情况下,反复缩小左边界,直到窗口再也不能缩小为止。//更新滑动窗口if(right - left < minLen) {minLeft = left;minLen = right - left;}// 在更新了窗口之后,尝试缩小窗口char lChar = s.charAt(left);if(TCharfrequency.containsKey(lChar)) {//存在可能性使得右边界右移的步数小于左边界右移的步数同时保持覆盖子串,//所以即使lChar 在 HashMap 中存在,仍然要移动左指针,更新hash表字符频率TCharfrequency.put(lChar, TCharfrequency.get(lChar) + 1); //如果hash表中包含左指针的字符,那么我们仍然移动右移左指针if(TCharfrequency.get(lChar) > 0) {count++;//更新待匹配次数计数器}}left++; }}//substring 左闭右开return minLen == Integer.MAX_VALUE ? "" : s.substring(minLeft, minLeft + minLen);}
}

为什么即使 lChar 在 HashMap 中存在,仍然要移动左指针?

是因为有可能右边界右移的步数小于左边界右移的步数,并且仍然保持覆盖子串,因此即使 lChar 在 HashMap 中存在,我们仍然需要继续移动左指针,尝试缩小窗口。

count 的更新和 Hashmap 频率更新的先后顺序,在处理右边界字符时,是先更新count 然后更新 Hashmap 频率,但是在处理左边界字符时,是先更新 Hashmap 频率,然后更新count,请你分析这种先后顺序的必要性

你观察得非常精准,的确在处理右边界和左边界时,countHashMap 频率的更新顺序不同。这种先后顺序是有必要的,因为它保证了算法在不同情况下的正确性和有效性。下面,我会详细分析为什么在右边界和左边界的处理过程中,countHashMap 的更新顺序要有所区别。

右边界处理顺序:先更新 count,再更新 HashMap 频率

java">if (map.containsKey(rChar)) {if (map.get(rChar) > 0) {count--;  // 如果当前字符在t中且频率大于0,说明它是需要匹配的,count减1}map.put(rChar, map.get(rChar) - 1);  // 更新字符频率,表示窗口已经包含了该字符
}

原因分析

  1. count 的含义
    count 表示窗口中还需要匹配的字符数量。也就是说,当 count > 0 时,窗口还未完全覆盖字符串 t 中的所有字符。当 count == 0 时,窗口恰好包含了 t 中的所有字符。

  2. 为什么先更新 count
    在右边界扩展时,首先判断当前窗口能否满足字符匹配的需求。如果当前字符 rChar 是目标字符串 t 中的字符,并且它在 HashMap 中的频率大于 0,说明这是一个仍然需要匹配的字符,因此我们首先更新 count,将它减 1,表示这个字符的匹配需求被满足了。

    这样做的原因是:

    • 防止遗漏有效字符:在 count-- 之前,我们需要确保这个字符是否真正是需要的。因为 map.get(rChar) > 0 代表这个字符在窗口中还不够,因此我们首先减去 count 表示该字符的匹配需求已被满足。
    • 保证窗口的完整性:如果先更新了 HashMap,可能会导致误判。假设先更新了 HashMap 的频率值(即减去 1),然后再去检查 count,此时可能会因为 HashMap 已经发生变化,而错过本应该减少的 count
  3. 更新 HashMap 频率
    在减少 count 之后,再更新 HashMap,将 rChar 的频率减 1,表示该字符已经被包含在窗口中。这一步保证了后续右指针继续移动时,窗口的状态是准确的。

左边界处理顺序:先更新 HashMap 频率,再更新 count

java">if (map.containsKey(lChar)) {map.put(lChar, map.get(lChar) + 1);  // 更新频率,表示移出窗口的字符需要重新匹配if (map.get(lChar) > 0) {count++;  // 如果频率变为正数,说明需要再次匹配该字符,count增加}
}

原因分析

  1. count 的含义保持不变
    count 在左边界移动时,仍然表示窗口中还需要匹配的字符数量。当窗口左边界字符被移出后,如果该字符属于 t 中的字符,我们需要判断移出该字符后,窗口中是否还包含足够数量的该字符。

  2. 为什么先更新 HashMap
    在左边界移动时,首先需要更新 HashMap 的频率,表示我们即将移除左边界的字符 lChar。如果 lChart 中的字符,我们将它的频率加 1,表示窗口中少了一个这样的字符。

    这样做的原因是:

    • 准确判断窗口状态:只有先更新了 HashMap,我们才能准确判断窗口中是否缺少了某个字符。如果不先更新 HashMap,就无法知道这个字符移出后窗口是否仍然有效(即是否还包含足够的 t 中字符)。
    • 避免过早增加 count:如果我们在更新 HashMap 之前就更新 count,可能会错误地增加 count,而事实上,窗口中的字符数量可能仍然是足够的。
  3. 更新 count
    HashMap 频率更新之后,我们检查频率是否变成了正数。如果是正数,说明窗口中的该字符数量已经不足,因此需要将 count 增加,表示该字符需要重新匹配。

顺序对比总结:

  1. 处理右边界时

    • 先更新 count 是为了确保在当前字符进入窗口时,能够及时检查它是否满足匹配需求。只有在 count 减少之后,才能表明该字符被正确匹配。
    • 后更新 HashMap 是为了保证窗口的状态准确地反映已经包含的字符频率。
  2. 处理左边界时

    • 先更新 HashMap 是为了表示当前字符即将从窗口中移除。只有更新了字符的频率之后,才能判断窗口是否还满足条件。
    • 后更新 count 是因为我们需要在频率变化之后才能判断该字符是否已经不足。如果移出字符后,窗口中还需要再次匹配该字符,那么 count 才会增加。

必要性总结:

  • 右边界的顺序确保了窗口扩展时能够正确减少 count,保证每次右边界移动后,我们都能够实时判断窗口是否满足匹配条件。
  • 左边界的顺序确保了在缩小窗口时,我们能够准确更新窗口的状态,防止误判窗口是否仍然满足所有条件。

这个先后顺序的设计,确保了算法在扩展和收缩窗口时,都能够正确地处理字符匹配问题,从而确保我们能找到最小的覆盖子串。


http://www.ppmy.cn/devtools/116615.html

相关文章

记软件开发者画图(UML),使用WPS应用制图

目录 前言 一、什么是UML 二、使用什么画图工具 三、示例 ​四、IntelliJ IDEA 2021快速生成UML图 前言 做软件开发的从写第一个示例程序到最后写项目程序避不开的需要设计画图&#xff0c;所以今天我们就来梳理一下‌UML&#xff08;统一建模语言&#xff09;图形需要画…

Idea集成docker实现镜像打包一键部署

1.Docker开启远程访问 #修改该Docker服务文件 vi /lib/systemd/system/docker.service#修改ExecStart这行 ExecStart/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock将文件内的 ExecStart注释。 新增如上行。 ExecStart/usr/bin/dockerd -H fd:/…

通信工程学习:什么是PON无源光网络

PON&#xff1a;无源光网络 PON&#xff08;Passive Optical Network&#xff0c;无源光纤网络&#xff09;是一种采用光分路器等无源光器件进行信号传输和分配的光纤接入技术。它利用光纤作为传输媒介&#xff0c;通过无源设备将光信号从中心局&#xff08;如光线路终端OLT&am…

【C++复习】C++特殊类总结

文章目录 不能被拷贝只在堆上创建只能在栈上创建不能被继承饿汉单例 程序启动前就创建好懒汉单例 需要的时候即调用GetInstance时再创建单例 不能被拷贝 class CopyBan {public:CopyBan(){}private:// C98CopyBan(const CopyBan&);CopyBan& operator(const CopyBan&am…

广州电影产业博览交易会将于本周五开始

“影动广州绽放世界”广州电影产业博览交易会由广州市人民政府主办&#xff0c;广州市委宣传部承办&#xff0c;将在广交会展馆A区4.2及5.2馆启幕。本届广州影博会聚焦电影产业交易、科技创新和消费市场&#xff0c;链接国内外电影资源&#xff0c;活动内容丰富。设置电影主题展…

Linux——K8s集群部署过程

&#xff11;、环境准备 &#xff08;1&#xff09;配置好网络ip和主机名 control: node1: node2: 配置ip 主机名的过程省略 配置一个简单的基于hosts文件的名称解析 [rootnode1 ~]# vim /etc/hosts // 文件中新增以下三行 192.168.110.10 control 192.168.110.11 node1 1…

OLED(1)原理篇

文章目录 1 OLED 硬件1.1 SSD1306 简介1.2 SSD1306 框图及引脚定义1.3 4-Pin 模块原理图&#xff08;I2C&#xff09;1.4 7-Pin 模块原理图&#xff08;SPI&#xff09; 2 通信协议2.1 6800 并口协议2.2 8080 并口协议2.3 4-wire SPI2.4 3-wire SPI2.5 I2C 3 总结4 参考 1 OLED…

Android String资源文件中,空格、换行以及特殊字符如何表示

空格&#xff1a; 例&#xff1a;<string name"test">test test</string> 换行&#xff1a;\n 例&#xff1a;<string name"test">test \n test</string> tab&#xff1a;\t …