题目: Given a string S and a string T, find the minimum window in S which will contain all the characters in T in complexity O(n).

  For example,
  S = "ADOBECODEBANC"
  T = "ABC"

  Minimum window is "BANC".

  Note:
  If there is no such window in S that covers all characters in T, return the emtpy string "".

  If there are multiple such windows, you are guaranteed that there will always be only one unique minimum window in S.

这道题其实还有一点没有说得太清楚,导致开始的解得不对。T里面是允许重复字母的。一个符合条件的Window至少要包含和T中一样数目的相应字母。例如例子中如果T是AABC,那Minimum Window 就是整个S了。

搞清了这一点再去做,其实还是想了很久的。主要需要想清楚下面几点。

  1. 扫描S的过程中,如何判断已经收集到所有目标字母,当前还差哪些字母要收集?正如我们在把妹的过程中mm经常问的: Where are we? 这里要注意,就是有可能T的某些字母还没有收集完毕,另一些字母可能远远超过了T需要的数目。
  2. 当我们知道已经收集到所有目标字母后,如何知道当前window的起点和终点,用以和以前记录下来的最小window做比较?
  3. 在确认出现一个window以后,再次继续扫描S(如果S还没有扫描完毕的话),如何更新当前已经收集到的目标字母集合?因为我们不可能一下跳过这个window,因为它的右边部分可能还能和未扫描的S部分组合成更小的window,那么肯定需要想办法把window做最小的右移。

这里有三个解法(本娃就figure out出来复杂度最高的),复杂度依次是O(N*M), O(NlogM)和O(N),其实都是用不同的方式来解决上面三点。

解法一:O(N*M)

我们用一个hash table(名字叫needToFill)来记录T中每一个字母出现的次数;一个hash table(名字叫charAppearenceRecorder)来存储扫描到当前位置 so far在S中出现的T字母的位置。因为可能一个字母需要出现多次,charAppearenceRecorder以T中的每个字母为key,value是一个LinkedList,每一个节点是一个整型的index,表示该字母在S中的位置;最后用一个hash table来为T中每一个字母表示成一个整数,例如T=“ABC”, 那么A=0,B=1,C=2,这样在扫描过程中,我们利用位操作一个整数表示当前某个字母是否已经收集完毕了。

在遍历S的过程中,如果某个字母c属于T,那么我们把它加入到charAppearenceRecorder对应字母的链表(尾)中。如果链表的长度等于了needToFill中记录的T要求的该字母的数目,那么记录c字母收集完毕(例如如果c 是A,我们利用位操作把第0为置1)。而如果链表的长度大于needToFill中记录的T要求的该字母的数目,我们删除对应字母的链表的头节点,也就是最早遇到的该字母的index。这样,charAppearenceRecorder始终保持合法数目的字母,同时,在超过要求数目的字母出现时候,总是选择靠右的合法数目的字母,以缩短window长度。

当发现一个合法的window时,我们可以通过遍历charAppearenceRecorder的所有链表的头节点,找出start index(here就是O(N*M)中的M来历了),更新最小window的起始点。

代码如下:

     public String minWindow(String S, String T) {
//记录T中每一个字母出现的次数
HashMap<Character, Integer> needToFill = new HashMap<Character, Integer>();
//记录S中出现的T字母的位置
HashMap<Character, LinkedList<Integer>> charAppearenceRecorder = new HashMap<Character, LinkedList<Integer>>();
HashMap<Character, Integer> charBit = new HashMap<Character, Integer>();
int bit_cnt = 0;
for(int i = 0; i < T.length(); i++){
if(needToFill.containsKey(T.charAt(i))){
needToFill.put(T.charAt(i), needToFill.get(T.charAt(i)) + 1);
}else {
needToFill.put(T.charAt(i), 1);
charBit.put(T.charAt(i), bit_cnt++);
charAppearenceRecorder.put(T.charAt(i), new LinkedList<Integer>());
}
}
long upper = (1 << bit_cnt) - 1;//当bit_status == upper时,表示收集完所有的字母
long bit_status = 0;
int minWinStart = -1;
int minWinEnd = S.length();
for(int i = 0; i < S.length(); i++){
char c = S.charAt(i);
if(needToFill.containsKey(c)){
LinkedList<Integer> charList = charAppearenceRecorder.get(c);
charList.add(i);
if(charList.size() == needToFill.get(c)){
//字母c已经收集完毕,那么我们设置c对应的位
bit_status |= (1 << charBit.get(c));
}
if(charList.size() > needToFill.get(c) && bit_status != upper){
charList.removeFirst();
}
if(bit_status == upper){//收集到了合法的一个window
int start = startIndex(charAppearenceRecorder);
if(i - start <= minWinEnd - minWinStart){
minWinEnd = i;
minWinStart = start;
}
char charToShift = S.charAt(start);
charList = charAppearenceRecorder.get(charToShift);
charList.removeFirst();
bit_status -= (1 << charBit.get(charToShift));
}
}
} return minWinStart == -1 ? "" : S.substring(minWinStart, minWinEnd + 1);
}

O(N*M)

举个栗子。

S=“acbbaca“ T=“aba”

当扫描到i=3的时候,遇到一个b。我们还没有遇到足够数量的a,但是b的数量,当加入当前的b以后,就超过了要求的数目。

于是我们删除charAppearenceRecorder中对应的b的第一个节点2,继续扫描。

这时候我们再次遇到a,这样,所有的T中的字母收集完毕,红色部分覆盖了一个合法的window,通过找到charAppearenceRecorder中的最小元素(蓝色部分),可以知道当前找到的window的长度4 - 0 + 1 = 5.因为之前没有合法window,所以当前最短就是5了。

在指针再次递进的之前,我们需要更新bit_status状态和charAppearenceRecorder。对charAppearenceRecorder,其实就是简单删除起始索引节点,同时在bit_status中重置对应的bit位。这样,我们表示在期待下一个a了,而且window总是最短的。

最后,我们移动到了6. 这也是一个合法的window。对比之前的长度,6-3+1 = 4明显小于5,所以最短的覆盖T中所有字母的window就是从3到6的这个window。

总结下这个方法:

1.使用bit位来表示收集到足够数目的字母;

2.合理的hash table和linkedlist运用。

3.不足的地方是每次需要在charAppearenceRecorder里面寻找最小的index,来计算window的长度,造成O(N*M)的时间复杂度。

下面一个系列,我们来讨论O(NlogM)的解法。

LeetCode 笔记系列16.1 Minimum Window Substring [从O(N*M), O(NlogM)到O(N),人生就是一场不停的战斗]的更多相关文章

  1. LeetCode 笔记系列16.3 Minimum Window Substring [从O(N*M), O(NlogM)到O(N),人生就是一场不停的战斗]

    题目:Given a string S and a string T, find the minimum window in S which will contain all the characte ...

  2. LeetCode 笔记系列16.2 Minimum Window Substring [从O(N*M), O(NlogM)到O(N),人生就是一场不停的战斗]

    题目:Given a string S and a string T, find the minimum window in S which will contain all the characte ...

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

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

  4. Minimum Window Substring @LeetCode

    不好做的一道题,发现String Algorithm可以出很多很难的题,特别是多指针,DP,数学推导的题.参考了许多资料: http://leetcode.com/2010/11/finding-mi ...

  5. LeetCode解题报告—— Minimum Window Substring && Largest Rectangle in Histogram

    1. Minimum Window Substring Given a string S and a string T, find the minimum window in S which will ...

  6. 【LeetCode】76. Minimum Window Substring

    Minimum Window Substring Given a string S and a string T, find the minimum window in S which will co ...

  7. 53. Minimum Window Substring

    Minimum Window Substring Given a string S and a string T, find the minimum window in S which will co ...

  8. leetcode76. Minimum Window Substring

    leetcode76. Minimum Window Substring 题意: 给定字符串S和字符串T,找到S中的最小窗口,其中将包含复杂度O(n)中T中的所有字符. 例如, S ="AD ...

  9. 刷题76. Minimum Window Substring

    一.题目说明 题目76. Minimum Window Substring,求字符串S中最小连续字符串,包括字符串T中的所有字符,复杂度要求是O(n).难度是Hard! 二.我的解答 先说我的思路: ...

随机推荐

  1. Java数据结构和算法(一):简介

    本系列博客我们将学习数据结构和算法,为什么要学习数据结构和算法,这里我举个简单的例子. 编程好比是一辆汽车,而数据结构和算法是汽车内部的变速箱.一个开车的人不懂变速箱的原理也是能开车的,同理一个不懂数 ...

  2. Cocos2d-x3.0游戏实例之《别救我》第六篇——从代码中获取UI控件

    这篇的内容非常easy,获取UI控件,然后使用它. 还记得我们在UI编辑器中给三个button分别命名了吧? 如今要用上了. 笨木头花心贡献,啥?花心?不呢,是用心~ 转载请注明,原文地址: http ...

  3. 人工打jar包

    (一)将可执行程序打成一个jar包 其中Yoyo为入口程序,因此将当前目录下workhard和Book.class.testEx.class.Yoyo.class打成一个jar包的命令如下: jar ...

  4. 如果本身kali就在局域网,shell在外网,怎么反弹连接呢?

    kali虚拟机使用桥接,路由器端口映射监听的端口即可.

  5. Pgsql排序让空值NULL排在数字后边

    遇到一种情况,对数字进行排序的时候,出现NULL在数字后面的情况,现在的需求是NULL排在前面然后才是升序的排数字 [Oracle 结论] order by colum asc 时,null默认被放在 ...

  6. [mmc/sdio]Linux下的sdio和mmc

    http://www.cnblogs.com/RandyQ/p/3607107.html

  7. CheckBox和richTextBox

    namespace WindowsFormsApplication1 { public partial class Form1 : Form { public Form1() { Initialize ...

  8. 使用模板方法模式简化JDBC操作

    在使用JDBC时,会重复的写很多重复的代码,例如 Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; S ...

  9. SenCha Touch HTML 5 应用程序缓存

    http://www.cnblogs.com/qidian10/p/3292876.html https://developer.mozilla.org/zh-CN/docs/HTML/Using_t ...

  10. JavaScrip——简单练习(输出方式,简单表单验证)

    <script> //输出方式 document.write(Date());//获取当前时间 document.write(1); document.write("<p& ...