LeetCode 笔记系列16.1 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 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了。
搞清了这一点再去做,其实还是想了很久的。主要需要想清楚下面几点。
- 扫描S的过程中,如何判断已经收集到所有目标字母,当前还差哪些字母要收集?正如我们在把妹的过程中mm经常问的: Where are we? 这里要注意,就是有可能T的某些字母还没有收集完毕,另一些字母可能远远超过了T需要的数目。
- 当我们知道已经收集到所有目标字母后,如何知道当前window的起点和终点,用以和以前记录下来的最小window做比较?
- 在确认出现一个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),人生就是一场不停的战斗]的更多相关文章
- 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 ...
- 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 ...
- LeetCode 76. 最小覆盖子串(Minimum Window Substring)
题目描述 给定一个字符串 S 和一个字符串 T,请在 S 中找出包含 T 所有字母的最小子串. 示例: 输入: S = "ADOBECODEBANC", T = "ABC ...
- Minimum Window Substring @LeetCode
不好做的一道题,发现String Algorithm可以出很多很难的题,特别是多指针,DP,数学推导的题.参考了许多资料: http://leetcode.com/2010/11/finding-mi ...
- 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 ...
- 【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 ...
- 53. Minimum Window Substring
Minimum Window Substring Given a string S and a string T, find the minimum window in S which will co ...
- leetcode76. Minimum Window Substring
leetcode76. Minimum Window Substring 题意: 给定字符串S和字符串T,找到S中的最小窗口,其中将包含复杂度O(n)中T中的所有字符. 例如, S ="AD ...
- 刷题76. Minimum Window Substring
一.题目说明 题目76. Minimum Window Substring,求字符串S中最小连续字符串,包括字符串T中的所有字符,复杂度要求是O(n).难度是Hard! 二.我的解答 先说我的思路: ...
随机推荐
- Python操作redis系列以 哈希(Hash)命令详解(四)
# -*- coding: utf-8 -*- import redis #这个redis不能用,请根据自己的需要修改 r =redis.Redis(host=") 1. Hset 命令用于 ...
- Python 实现小数和百分数的相互转换
# -*- coding: utf-8 -*- #百分比转换位小数 # -*- coding: utf-8 -*- s = '20%' # 默认要转换的百分比是字符串aa = float(s.stri ...
- php类自动载入
在编写面向对象(OOP) 程序时,很多开发者为每个类新建一个 PHP 文件. 这会带来一个烦恼:每个脚本的开头,都需要包含(include)一个长长的列表(每个类都有个文件). 在 PHP 5 中,已 ...
- ubuntu16.04字体设置
我在ubuntu下主要有2个应用,一个是终端,一个是firefox浏览器 1.在终端下,我喜欢“Courier 10 pitch regular”,字号是14.Courier是一种专门为程序员设计的字 ...
- Nginx的Rewrite正则表达式,匹配非某单词
Nginx的Rewrite正则表达式,匹配非某单词 由于要rewrite一个地址从 /mag/xx/xxx/ -> /m/xxx 但原先 /mag/xx/more/ 要保留 这就得写一个比较奇特 ...
- 每日英语:China's Bad Earth
In Dapu, a rain-drenched rural outpost in the heart of China's grain basket, a farmer grows crops th ...
- CentOS6.4下Docker应用环境的部署配置
http://blog.chinaunix.net/uid-619485-id-4973941.html *********************************************** ...
- location.href 不响应的解决方法
在某些时候修改元素的值的时候过快会导致失效 1.window.open("","_blank") 代替 不过也是打开新窗口 2.延迟调用location.hr ...
- wcf客户端终结点样本集合
1. <bindings> <basicHttpBinding> <binding name="BasicHttpBinding_IUser" /&g ...
- Unix系统编程()检查进程的存在
检查进程的存在 kill系统调用还有另一重功用.若将参数sig指定为0(即所谓空信号),则无信号发送. 相反,kill仅会去执行错误检查,查看是否可以向目标进程发送信号. 从另一角度来看,这意味着,可 ...