题目: 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的起始点。

代码如下:

  1. public String minWindow(String S, String T) {
  2. //记录T中每一个字母出现的次数
  3. HashMap<Character, Integer> needToFill = new HashMap<Character, Integer>();
  4. //记录S中出现的T字母的位置
  5. HashMap<Character, LinkedList<Integer>> charAppearenceRecorder = new HashMap<Character, LinkedList<Integer>>();
  6. HashMap<Character, Integer> charBit = new HashMap<Character, Integer>();
  7. int bit_cnt = 0;
  8. for(int i = 0; i < T.length(); i++){
  9. if(needToFill.containsKey(T.charAt(i))){
  10. needToFill.put(T.charAt(i), needToFill.get(T.charAt(i)) + 1);
  11. }else {
  12. needToFill.put(T.charAt(i), 1);
  13. charBit.put(T.charAt(i), bit_cnt++);
  14. charAppearenceRecorder.put(T.charAt(i), new LinkedList<Integer>());
  15. }
  16. }
  17. long upper = (1 << bit_cnt) - 1;//当bit_status == upper时,表示收集完所有的字母
  18. long bit_status = 0;
  19. int minWinStart = -1;
  20. int minWinEnd = S.length();
  21. for(int i = 0; i < S.length(); i++){
  22. char c = S.charAt(i);
  23. if(needToFill.containsKey(c)){
  24. LinkedList<Integer> charList = charAppearenceRecorder.get(c);
  25. charList.add(i);
  26. if(charList.size() == needToFill.get(c)){
  27. //字母c已经收集完毕,那么我们设置c对应的位
  28. bit_status |= (1 << charBit.get(c));
  29. }
  30. if(charList.size() > needToFill.get(c) && bit_status != upper){
  31. charList.removeFirst();
  32. }
  33. if(bit_status == upper){//收集到了合法的一个window
  34. int start = startIndex(charAppearenceRecorder);
  35. if(i - start <= minWinEnd - minWinStart){
  36. minWinEnd = i;
  37. minWinStart = start;
  38. }
  39. char charToShift = S.charAt(start);
  40. charList = charAppearenceRecorder.get(charToShift);
  41. charList.removeFirst();
  42. bit_status -= (1 << charBit.get(charToShift));
  43. }
  44. }
  45. }
  46.  
  47. return minWinStart == -1 ? "" : S.substring(minWinStart, minWinEnd + 1);
  48. }

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. Python操作redis系列以 哈希(Hash)命令详解(四)

    # -*- coding: utf-8 -*- import redis #这个redis不能用,请根据自己的需要修改 r =redis.Redis(host=") 1. Hset 命令用于 ...

  2. Python 实现小数和百分数的相互转换

    # -*- coding: utf-8 -*- #百分比转换位小数 # -*- coding: utf-8 -*- s = '20%' # 默认要转换的百分比是字符串aa = float(s.stri ...

  3. php类自动载入

    在编写面向对象(OOP) 程序时,很多开发者为每个类新建一个 PHP 文件. 这会带来一个烦恼:每个脚本的开头,都需要包含(include)一个长长的列表(每个类都有个文件). 在 PHP 5 中,已 ...

  4. ubuntu16.04字体设置

    我在ubuntu下主要有2个应用,一个是终端,一个是firefox浏览器 1.在终端下,我喜欢“Courier 10 pitch regular”,字号是14.Courier是一种专门为程序员设计的字 ...

  5. Nginx的Rewrite正则表达式,匹配非某单词

    Nginx的Rewrite正则表达式,匹配非某单词 由于要rewrite一个地址从 /mag/xx/xxx/ -> /m/xxx 但原先 /mag/xx/more/ 要保留 这就得写一个比较奇特 ...

  6. 每日英语:China's Bad Earth

    In Dapu, a rain-drenched rural outpost in the heart of China's grain basket, a farmer grows crops th ...

  7. CentOS6.4下Docker应用环境的部署配置

    http://blog.chinaunix.net/uid-619485-id-4973941.html *********************************************** ...

  8. location.href 不响应的解决方法

    在某些时候修改元素的值的时候过快会导致失效 1.window.open("","_blank") 代替  不过也是打开新窗口 2.延迟调用location.hr ...

  9. wcf客户端终结点样本集合

    1. <bindings> <basicHttpBinding> <binding name="BasicHttpBinding_IUser" /&g ...

  10. Unix系统编程()检查进程的存在

    检查进程的存在 kill系统调用还有另一重功用.若将参数sig指定为0(即所谓空信号),则无信号发送. 相反,kill仅会去执行错误检查,查看是否可以向目标进程发送信号. 从另一角度来看,这意味着,可 ...