https://leetcode-cn.com/problems/minimum-window-substring/solution/hua-dong-chuang-kou-suan-fa-tong-yong-si-xiang-by-/ (滑动窗口通用思想)

描述

给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字母的最小子串。

示例:

输入: S = "ADOBECODEBANC", T = "ABC"
输出: "BANC"
说明:

如果 S 中不存这样的子串,则返回空字符串 ""。
如果 S 中存在这样的子串,我们保证它是唯一的答案。

解析

题目不难理解,就是说要在 S(source) 中找到包含 T(target) 中全部字母的一个子串,顺序无所谓,但这个子串一定是所有可能子串中最短的。

如果我们使用暴力解法,代码大概是这样的:

for (int i = 0; i < s.size(); i++)
for (int j = i + 1; j < s.size(); j++)
if s[i:j] 包含 t 的所有字母:
更新答案

思路很直接吧,但是显然,这个算法的复杂度肯定大于 O(N^2) 了,不好。

滑动窗口算法的思路是这样:

1、我们在字符串 S 中使用双指针中的左右指针技巧,初始化 left = right = 0,把索引闭区间 [left, right] 称为一个「窗口」。

2、我们先不断地增加 right 指针扩大窗口 [left, right],直到窗口中的字符串符合要求(包含了 T 中的所有字符)。

3、此时,我们停止增加 right,转而不断增加 left 指针缩小窗口 [left, right],直到窗口中的字符串不再符合要求(不包含 T 中的所有字符了)。同时,每次增加 left,我们都要更新一轮结果。

4、重复第 2 和第 3 步,直到 right 到达字符串 S 的尽头。

这个思路其实也不难,第 2 步相当于在寻找一个「可行解」,然后第 3 步在优化这个「可行解」,最终找到最优解。左右指针轮流前进,窗口大小增增减减,窗口不断向右滑动。

下面画图理解一下,needs 和 window 相当于计数器,分别记录 T 中字符出现次数和窗口中的相应字符的出现次数。

初始状态:

增加 right,直到窗口 [left, right] 包含了 T 中所有字符:

现在开始增加 left,缩小窗口 [left, right]。

直到窗口中的字符串不再符合要求,left 不再继续移动。

之后重复上述过程,先移动 right,再移动 left…… 直到 right 指针到达字符串 S 的末端,算法结束。

如果你能够理解上述过程,恭喜,你已经完全掌握了滑动窗口算法思想。至于如何具体到问题,如何得出此题的答案,都是编程问题,等会提供一套模板,理解一下就会了。

上述过程可以简单地写出如下伪码框架:

string s, t;
// 在 s 中寻找 t 的「最小覆盖子串」
int left = 0, right = 0;
string res = s; while(right < s.size()) {
window.add(s[right]);
right++;
// 如果符合要求,移动 left 缩小窗口
while (window 符合要求) {
// 如果这个窗口的子串更短,则更新 res
res = minLen(res, window);
window.remove(s[left]);
left++;
}
}
return res;

如果上述代码你也能够理解,那么你离解题更近了一步。现在就剩下一个比较棘手的问题:如何判断 window 即子串 s[left...right] 是否符合要求,是否包含 t 的所有字符呢?

可以用两个哈希表当作计数器解决。用一个哈希表 needs 记录字符串 t 中包含的字符及出现次数,用另一个哈希表 window 记录当前「窗口」中包含的字符及出现的次数,如果 window 包含所有 needs 中的键,且这些键对应的值都大于等于 needs 中的值,那么就可以知道当前「窗口」符合要求了,可以开始移动 left 指针了。

如果直接甩给你这么一大段代码,我想你的心态是爆炸的,但是通过之前的步步跟进,你是否能够理解这个算法的内在逻辑呢?你是否能清晰看出该算法的结构呢?

这个算法的时间复杂度是 O(M + N)O(M+N),MM 和 NN 分别是字符串 SS 和 TT 的长度。因为我们先用 forfor 循环遍历了字符串 TT 来初始化 needsneeds,时间 O(N)O(N),之后的两个 whilewhile 循环最多执行 2M2M 次,时间 O(M)O(M)。

也许认为嵌套的 while 循环复杂度应该是平方级,但是你这样想,while 执行的次数就是双指针 left 和 right 走的总路程,最多是 2M 嘛。

代码

class Solution {
public String minWindow(String s, String target) {
if (s == null || target == null || s.length() == 0
|| target.length() == 0 || target.length() > s.length()) {
return "";
}
Map<Character, Integer> needs = new HashMap<>();
for (char ch : target.toCharArray()) {
int nums = needs.getOrDefault(ch, 0);
needs.put(ch, nums + 1);
}
int sLength = s.length();
int tLength = target.length();
int left = 0;//左指针
int right = 0;//右指针
int resLeftRight = Integer.MAX_VALUE;//保存最小的left、right的间隔
int minLeft = 0;//保存间隔最小的符合条件的left
Map<Character, Integer> windows = new HashMap<>();
while (right < sLength) {
char curChar = s.charAt(right);
int curNum = windows.getOrDefault(curChar, 0);
windows.put(curChar, curNum + 1);
right++;
while (right - left >= tLength && minWindowHelp(windows, needs)) {
int curLeftRight = right - left;
if (curLeftRight < resLeftRight) {
//可能有很多符合条件的,比较出left、right间隔最小的
minLeft = left;
resLeftRight = curLeftRight;
}
char leftChar = s.charAt(left);
int leftCharNum = windows.get(leftChar);
if (leftCharNum == 1) {
windows.remove(leftChar);
} else {
windows.put(leftChar, leftCharNum - 1);
}
left++;
}
}
return resLeftRight == Integer.MAX_VALUE ? ""
: s.substring(minLeft, minLeft + resLeftRight);
} public boolean minWindowHelp(Map<Character, Integer> windows, Map<Character, Integer> needs) {
if (windows.size() < needs.size()) {
return false;
}
for (Map.Entry<Character, Integer> entry : needs.entrySet()) {
Character key = entry.getKey();
int num = entry.getValue();
if (!windows.containsKey(key) || windows.get(key) < num) {
return false;
}
}
return true;
}
}

[LeetCode] 76. 最小覆盖子串 ☆☆☆☆☆(滑动窗口)的更多相关文章

  1. Java实现 LeetCode 76 最小覆盖子串

    76. 最小覆盖子串 给你一个字符串 S.一个字符串 T,请在字符串 S 里面找出:包含 T 所有字母的最小子串. 示例: 输入: S = "ADOBECODEBANC", T = ...

  2. Leetcode 76.最小覆盖子串

    最小覆盖子串 给定一个字符串 S 和一个字符串 T,请在 S 中找出包含 T 所有字母的最小子串. 示例: 输入: S = "ADOBECODEBANC", T = "A ...

  3. 【leetcode 76. 最小覆盖子串】解题报告

    思路:滑动窗口思想 方法一:滑动窗口 string minWindow(string s, string t) { // 1.tdict记录T中每个字母与字母个数 // 2.维护一个滑动窗口字母的计数 ...

  4. LeetCode 76. 最小覆盖子串(Minimum Window Substring)

    题目描述 给定一个字符串 S 和一个字符串 T,请在 S 中找出包含 T 所有字母的最小子串. 示例: 输入: S = "ADOBECODEBANC", T = "ABC ...

  5. leetcode 76最小覆盖子串

    time O(n) spaceO(n) 的方法: 还是借助哈希表,所有字母初始化为0,将t中出现的所有字母次数全都记录在哈希表里: 采用双指针,分别为一个头指针head,和尾指针tail.flag记录 ...

  6. [LeetCode]438. 找到字符串中所有字母异位词、76. 最小覆盖子串(滑动窗口解决子串问题系列)

    题目438. 找到字符串中所有字母异位词 给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引. 说明: 字母异位词指字母相同,但排列不同的字符 ...

  7. 【LeetCode】76. 最小覆盖子串

    76. 最小覆盖子串 知识点:字符串:滑动窗口 题目描述 给你一个字符串 s .一个字符串 t .返回 s 中涵盖 t 所有字符的最小子串.如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 ...

  8. leetcode的Hot100系列--3. 无重复字符的最长子串--滑动窗口

    可以先想下这两个问题: 1.怎样使用滑动窗口? 2.如何快速的解决字符查重问题? 滑动窗口 可以想象一下有两个指针,一个叫begin,一个叫now 这两个指针就指定了当前正在比较无重复的字符串,当再往 ...

  9. [LeetCode]3. 无重复字符的最长子串(滑动窗口)

    题目 给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度. 示例 1: 输入: "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc ...

随机推荐

  1. SQLite数据库数据类型详解

    数据类型 类型 描             述 bit 整型 bit 数据类型是整型,其值只能是0.1或空值.这种数据类型用于存储只有两种可能值的数据,如Yes 或No.True 或Fa lse .O ...

  2. 通过直方图进行PCA准备

    import graphviz import mglearn from mpl_toolkits.mplot3d import Axes3D from sklearn.datasets import ...

  3. 微信小程序技巧记录

    1.直接在app.json中添加pages,会自动按照路径生成page目录文件: 2.动态修改样式: /** * 页面的初始数据 */ data: { authorInfo: [], article: ...

  4. jpa序号/数字占位符?1/?2

    这里的入参nodeId对应占位符?1,入参severity对应?2:缺点是,序号必须是顺序的,按参数顺序严格对应 @Modifying @Transactional @Query(value = &q ...

  5. python:动态参数*args

    动态参数 顾名思义,动态参数就是传入的参数的个数是动态的,可以是1个.2个到任意个,还可以是0个.在不需要的时候,你完全可以忽略动态函数,不用给它传递任何值. Python的动态参数有两种,分别是*a ...

  6. linux 使用jar 打包成war

    把当前目录下的所有文件打包成game.war jar -cvfM0 game.war ./ -c   创建war包 -v   显示过程信息 -f     -M -0   这个是阿拉伯数字,只打包不压缩 ...

  7. 2019.10.28 IDEA入门指南(很多人问补充一篇)

    Idea快速入门指南 1.安装 1.1.安装 我们使用的是最新的2017.3.4版本: 双击打开, 选择一个目录,最好不要中文和空格: 然后选择桌面快捷方式,请选择64位: 然后选择安装: 开始安装: ...

  8. 任务调度之Quartz.Net基础

    最近公司要求将之前的系统设计文档补上,于是大家就都被分配了不同的任务,紧锣密鼓的写起了文档来.发现之前项目中使用了Quartz.Net来做一些定时任务的调度,比如定时的删除未支付的订单,定时检查支付状 ...

  9. vue之$event获取当前元素的节点

    <p @click = “clickfun($event)”>点击</p> methods: { clickfun(e) { // e.target 是你当前点击的元素 // ...

  10. php mqtt client

    <?php /* phpMQTT */ class phpMQTT { private $socket; /* holds the socket */ private $msgid = 1; / ...