当前位置: 首页 > news >正文

滑动窗口题目:最小覆盖子串

文章目录

  • 题目
    • 标题和出处
    • 难度
    • 题目描述
      • 要求
      • 示例
      • 数据范围
      • 进阶
  • 解法一
    • 思路和算法
    • 证明
    • 代码
    • 复杂度分析
  • 解法二
    • 思路和算法
    • 代码
    • 复杂度分析

题目

标题和出处

标题:最小覆盖子串

出处:76. 最小覆盖子串

难度

6 级

题目描述

要求

给定两个字符串 s\texttt{s}st\texttt{t}t,长度分别是 m\texttt{m}mn\texttt{n}n,返回 s\texttt{s}s 中覆盖 t\texttt{t}t 中的每个字符(包括重复字符)的最小子串。如果没有这样的子串,返回空字符串 ""\texttt{""}""

题目确保答案唯一

子串是字符串中的连续字符序列。

示例

示例 1:

输入:s="ADOBECODEBANC",t="ABC"\texttt{s = "ADOBECODEBANC", t = "ABC"}s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"\texttt{"BANC"}"BANC"
解释:最小子串 "BANC"\texttt{"BANC"}"BANC" 包含字符串 t\texttt{t}t‘A’\texttt{`A'}‘A’‘B’\texttt{`B'}‘B’‘C’\texttt{`C'}‘C’

示例 2:

输入:s="a",t="a"\texttt{s = "a", t = "a"}s = "a", t = "a"
输出:"a"\texttt{"a"}"a"
解释:整个字符串 s\texttt{s}s 是最小子串。

示例 3:

输入:s="a",t="aa"\texttt{s = "a", t = "aa"}s = "a", t = "aa"
输出:""\texttt{""}""
解释:字符串 t\texttt{t}t 中的两个 ‘a’\texttt{`a'}‘a’ 都必须包含在子串中。由于 s\texttt{s}s 的最大子串只有一个 ‘a’\texttt{`a'}‘a’,返回空字符串。

数据范围

  • m=s.length\texttt{m} = \texttt{s.length}m=s.length
  • n=t.length\texttt{n} = \texttt{t.length}n=t.length
  • 1≤m,n≤105\texttt{1} \le \texttt{m, n} \le \texttt{10}^\texttt{5}1m, n105
  • s\texttt{s}st\texttt{t}t 由大写和小写英语字母组成

进阶

你可以想出一个时间复杂度 O(m+n)\texttt{O(m + n)}O(m + n) 的算法吗?

解法一

思路和算法

如果字符串 sss 的下标范围 [start,end][\textit{start}, \textit{end}][start,end] 的子串覆盖字符串 ttt 中的每个字符,则将子串的左端点向左移动或将子串的右端点向右移动之后,得到更长的子串,同样覆盖字符串 ttt 中的每个字符。考虑两个不同下标 end1\textit{end}_1end1end2\textit{end}_2end2,其中 end1<end2\textit{end}_1 < \textit{end}_2end1<end2,分别以这两个下标作为结束下标,寻找最小覆盖子串,将这两个最小覆盖子串的开始下标分别记为 start1\textit{start}_1start1start2\textit{start}_2start2,则必有 start1≤start2\textit{start}_1 \le \textit{start}_2start1start2。因此,需要对每个下标寻找以该下标作为结束下标的最小覆盖子串。以下将覆盖字符串 ttt 中的每个字符的子串称为「覆盖子串」。

可以使用变长滑动窗口寻找字符串 sss 中的以每个下标作为结束下标的最小覆盖子串。用 [start,end][\textit{start}, \textit{end}][start,end] 表示滑动窗口,初始时 start=end=0\textit{start} = \textit{end} = 0start=end=0。将滑动窗口的右端点 end\textit{end}end 向右移动,移动过程中维护滑动窗口的左端点 start\textit{start}start,对于每个 end\textit{end}end 寻找最小覆盖子串。

判断一个子串是否为覆盖子串的方法是,使用哈希表记录每个字符在子串中的出现次数与在 ttt 中的出现次数之差,哈希表只记录在 ttt 中出现的字符,如果每个字符的出现次数之差都非负则子串是覆盖子串,否则子串不是覆盖子串。

初始时,最小覆盖字串的开始下标为 −1-11,长度为 +∞+\infty+

首先遍历字符串 ttt,将 ttt 中的每个字符在哈希表中的出现次数减 111,然后使用滑动窗口遍历字符串 sss。对于每个右端点 end\textit{end}end,执行如下操作。

  1. 如果 s[end]s[\textit{end}]s[end] 在哈希表中,则将该字符在哈希表中的次数加 111

  2. 如果滑动窗口 [start,end][\textit{start}, \textit{end}][start,end] 中的子串是覆盖子串,执行如下操作,直到滑动窗口 [start,end][\textit{start}, \textit{end}][start,end] 中的子串不是覆盖子串。

    1. 如果当前子串的长度小于最小覆盖子串的长度,则将最小覆盖子串的开始下标更新为 start\textit{start}start,长度更新为 end−start+1\textit{end} - \textit{start} + 1endstart+1

    2. 如果 s[start]s[\textit{start}]s[start] 在哈希表中,则将该字符在哈希表中的次数减 111

    3. start\textit{start}start 向右移动一位。

  3. 此时的滑动窗口 [start,end][\textit{start}, \textit{end}][start,end] 中的子串为以 end\textit{end}end 作为结束下标的最大非覆盖子串。如果 start>0\textit{start} > 0start>0,则以 end\textit{end}end 作为结束下标的最小覆盖子串为下标范围 [start−1,end][\textit{start} - 1, \textit{end}][start1,end] 的子串,该子串已经被遍历到。

遍历结束之后,即可得到字符串 sss 中的最小覆盖子串。

特别地,如果遍历结束之后,最小覆盖字串的开始下标为 −1-11,则字符串 sss 中不存在覆盖子串,返回空字符串。

证明

为了证明上述解法的正确性,需要证明对于每个 start\textit{start}start,其对应的 end\textit{end}end 满足下标范围 [start,end][\textit{start}, \textit{end}][start,end] 中的子串为以 start\textit{start}start 作为开始下标的最小覆盖子串。这里的对应表示当结束下标位于 end\textit{end}end 时,开始下标会经过 start\textit{start}start

假设以 start\textit{start}start 作为开始下标的最小覆盖子串的下标范围不是 [start,end][\textit{start}, \textit{end}][start,end],则存在下标 end′<end\textit{end}' < \textit{end}end<end 使得下标范围 [start,end′][\textit{start}, \textit{end}'][start,end] 中的子串为以 start\textit{start}start 作为开始下标的最小覆盖子串。当结束下标位于 end′\textit{end}'end 时,对于所有 start′≤start\textit{start}' \le \textit{start}startstart,下标范围 [start′,end′][\textit{start}', \textit{end}'][start,end] 中的子串都是覆盖子串,因此开始下标会向右移动到 start\textit{start}start 右边。当结束下标位于 end\textit{end}end 时,开始下标已经大于 start\textit{start}start,与 start\textit{start}start 对应 end\textit{end}end 矛盾。

因此,以 start\textit{start}start 作为开始下标的最小覆盖子串的下标范围是 [start,end][\textit{start}, \textit{end}][start,end]

代码

class Solution {public String minWindow(String s, String t) {int minStart = -1;int minLength = Integer.MAX_VALUE;Map<Character, Integer> counts = new HashMap<Character, Integer>();int m = s.length(), n = t.length();for (int i = 0; i < n; i++) {char c = t.charAt(i);counts.put(c, counts.getOrDefault(c, 0) - 1);}int start = 0, end = 0;while (end < m) {char curr = s.charAt(end);if (counts.containsKey(curr)) {counts.put(curr, counts.getOrDefault(curr, 0) + 1);}while (allIncluded(counts)) {if (end - start + 1 < minLength) {minStart = start;minLength = end - start + 1;}char prev = s.charAt(start);if (counts.containsKey(prev)) {counts.put(prev, counts.get(prev) - 1);}start++;}end++;}return minStart < 0 ? "" : s.substring(minStart, minStart + minLength);}public boolean allIncluded(Map<Character, Integer> counts) {Set<Map.Entry<Character, Integer>> entries = counts.entrySet();for (Map.Entry<Character, Integer> entry : entries) {if (entry.getValue() < 0) {return false;}}return true;}
}

复杂度分析

  • 时间复杂度:O(m×∣Σ∣+n)O(m \times |\Sigma| + n)O(m×∣Σ∣+n),其中 mmmnnn 分别是字符串 sssttt 的长度,Σ\SigmaΣ 是字符集,这道题中 Σ\SigmaΣ 是全部大写和小写英语字母,∣Σ∣=52|\Sigma| = 52∣Σ∣=52。需要首先遍历字符串 ttt 计算每个字符的次数,然后使用滑动窗口遍历字符串 sss,滑动窗口的左右端点最多各遍历字符串 sss 一次,每次移动需要 O(∣Σ∣)O(|\Sigma|)O(∣Σ∣) 的时间判断当前子串是否为覆盖子串,因此时间复杂度是 O(m×∣Σ∣+n)O(m \times |\Sigma| + n)O(m×∣Σ∣+n)

  • 空间复杂度:O(∣Σ∣)O(|\Sigma|)O(∣Σ∣),其中 Σ\SigmaΣ 是字符集,这道题中 Σ\SigmaΣ 是全部大写和小写英语字母,∣Σ∣=52|\Sigma| = 52∣Σ∣=52。哈希表需要 O(∣Σ∣)O(|\Sigma|)O(∣Σ∣) 的空间。

解法二

思路和算法

解法一判断每个子串是否为覆盖子串需要遍历字符串 ttt 中的每个不同字符。由于每次移动时,只会有一个字符的出现次数有变化,因此可以将每个子串的判断时间降低到 O(1)O(1)O(1)

遍历字符串 ttt 并更新哈希表之后,哈希表中的字符个数即为字符串 ttt 中的不同字符个数。维护一个计数器统计出现次数之差非负的字符个数,则可根据计数器的值判断当前子串是否为覆盖子串。

初始时,计数器的值为 000。对于每个在字符串 ttt 中出现的字符,每次更新字符在哈希表中的次数之后,需要更新计数器的值,更新方法如下。

  • 当一个字符在哈希表中的次数加 111 之后,如果次数变成 000,则将计数器的值加 111

  • 当一个字符在哈希表中的次数减 111 之后,如果次数变成 −1-11,则将计数器的值减 111

根据计数器的值即可快速判断当前子串是否为覆盖子串。当计数器的值等于字符串 ttt 中的不同字符个数时,当前子串为覆盖子串,否则当前子串不为覆盖子串。由此可以不需要遍历哈希表,在 O(1)O(1)O(1) 时间内判断每个子串是否为覆盖子串。

代码

class Solution {public String minWindow(String s, String t) {int minStart = -1;int minLength = Integer.MAX_VALUE;Map<Character, Integer> counts = new HashMap<Character, Integer>();int m = s.length(), n = t.length();for (int i = 0; i < n; i++) {char c = t.charAt(i);counts.put(c, counts.getOrDefault(c, 0) - 1);}int total = counts.size();int meets = 0;int start = 0, end = 0;while (end < m) {char curr = s.charAt(end);if (counts.containsKey(curr)) {counts.put(curr, counts.getOrDefault(curr, 0) + 1);if (counts.get(curr) == 0) {meets++;}}while (meets == total) {if (end - start + 1 < minLength) {minStart = start;minLength = end - start + 1;}char prev = s.charAt(start);if (counts.containsKey(prev)) {counts.put(prev, counts.get(prev) - 1);if (counts.get(prev) < 0) {meets--;}}start++;}end++;}return minStart < 0 ? "" : s.substring(minStart, minStart + minLength);}
}

复杂度分析

  • 时间复杂度:O(m+n)O(m + n)O(m+n),其中 mmmnnn 分别是字符串 sssttt 的长度。需要首先遍历字符串 ttt 计算每个字符的次数,然后使用滑动窗口遍历字符串 sss,滑动窗口的左右端点最多各遍历字符串 sss 一次,每次移动需要 O(1)O(1)O(1) 的时间判断当前子串是否为覆盖子串,因此时间复杂度是 O(m+n)O(m + n)O(m+n)

  • 空间复杂度:O(∣Σ∣)O(|\Sigma|)O(∣Σ∣),其中 Σ\SigmaΣ 是字符集,这道题中 Σ\SigmaΣ 是全部大写和小写英语字母,∣Σ∣=52|\Sigma| = 52∣Σ∣=52。哈希表需要 O(∣Σ∣)O(|\Sigma|)O(∣Σ∣) 的空间。

http://www.dtcms.com/a/434518.html

相关文章:

  • 【读书笔记】《深奥的简洁》
  • 支付宝手机网站支付贵阳门户网站
  • macOS 终端配置全攻略:zsh、bash_profile、zprofile、zshrc 到 nvm 配置的完整科普
  • Matlab通过GUI实现点云的Loss配准
  • 用户体验最好的网站做网站 参考文献
  • 离散与连续随机变量
  • 自适应网站好吗电子商务seo名词解释
  • 深入剖析 MySQL 中 binlog 与 redolog:区别、联系及在数据更新中的作用​
  • tensor转numpy,numpy转tensor
  • [创业之路-653]:社会产品与服务的分类
  • 洛谷题解P1249 最大乘积
  • Java使用easypoi填充数据到word
  • 位运算题3:将整数奇偶位互换
  • 计算机毕业设计 基于EChants的海洋气象数据可视化平台设计与实现 Python 大数据毕业设计 Hadoop毕业设计选题【附源码+文档报告+安装调试】
  • 卡盟网站顶图怎么做西安知名的集团门户网站建设公司
  • Python基础入门例程84-NP84 列表中第一次出现的位置
  • 基于腾讯CODING Maven的Android库发布
  • 郑州官方发布班级优化大师
  • 10.2 刷题知识点总结(1) ---- 正则表达式
  • SPI总线介绍
  • 【LeetCode_206】反转链表
  • 品牌网站建设 磐石网络的确好沈阳唐朝网络的服务内容
  • LeetCode 215. 数组中的第K个最大元素
  • 老年实训室建设:筑牢老年护理专业学员的实操能力基础
  • 【深度学习新浪潮】基于Qwen3-8B入门LoRA完整指南
  • 数据库设计与UML图
  • 影视网站的设计与实现新泰程序开发
  • 阿里pdf解析方案Logics-Parsing如何用RL攻克复杂文档解析
  • MySQL 索引失效的常见场景与原因分析
  • 四川省建设厅网站川北医学院网页制作员工作厂家