每天一道LeetCode-----在字符串s中找到最短的包含字符串t中所有字符的子串,子串中字符顺序无要求且可以有其他字符
2017-12-06 14:53
676 查看
Minimum Window Substring
原题链接Minimum Window Substring要求在源字符串s中找到长度最短的子串,这个子串包含目标字符串t中的所有字符,字符顺序没有要求。
注意在找到的子串中可以包含t中没有的字符。
乍一看是滑动窗的问题,如果题目要求是”在s中找到子串t,t中字符顺序无要求”,那么只需要维护一个长度为t的大小的滑动窗遍历s即可,这种做法要求找到的子串长度就是t的长度,且无其他t中没有的字符。
但是难点在本题可以存在t中没有的字符,比如示例中找到的子串是”BANC”,其中”BAC”是t,而’N’不属于t。所以,滑动窗的大小是需要动态变化的。
根据滑动窗的概念,解决问题时需要两个指针begin和end分别指向窗口的左边界和右边界,在向右移动的过程中右边加,左边减,直到满足条件即可。
但是,由于本题的滑动窗大小不固定,所以在向右移动的过程中,只能右边加,而不能左边减。只有当窗口此时覆盖的区域包含t时,才执行左边减。
不过,对于右边加左边减,仍然不是简单的将左边的字符删掉,将右边的字符添加进来。
考虑本题,题目中要求找到最短的窗口,这个窗口拥有t中所有的字符。同时根据end的增长,可以确定当窗口覆盖的区域包含t中所有字符以进行左边减时,end指向的字符一定在t中。
但是,没有办法确定begin指向的字符是否在t中,换句话说,程序一开始只是end在移动,当确定窗口中包含t中所有字符后开始执行左边减。但是begin的那个位置就没有动过,不一定就是t中的字符。
所以,在将左边界指向的字符删除后,如果当前的窗口仍然包含t中所有字符,那么可以继续将左边界删掉,直到窗口中缺少t中的某个字符时,在进行向右移动。
算法的难点就在于,如果确定当前窗口包含t中所有字符,由如何确定当前窗口缺少了t中的某个字符,这个字符是什么。
首先,定义一个容器,这个容器记录着当前滑动窗缺少t中哪几个字符,每个字符缺少多少个。这个容器可以是map,也可以是vector,这里定义成
vector<int> map(128, 0);
对于这个容器,规定
对于字符ch,map[ch]表示的是当前滑动窗缺少几个ch
如果map[ch]大于0,说明缺少map[ch]个ch
如果map[ch]小于0,说明滑动窗中富余|map[ch]|个ch(map[ch]的绝对值个)
如果map[ch]等于0,说明滑动窗中不缺少字符ch,也不富余字符ch
对于这个容器的操作,规定
通过end遍历到的字符ch,因为是将end遍历到的字符添加到滑动窗中,所以执行map[ch]–,即代表加进来一个ch,那么对于ch的缺少数量就应该减一
通过begin遍历到的字符ch,因为是将begin遍历到的字符从滑动窗中删除,所以执行map[ch]++,即代表删掉一个ch,那么对于ch的缺少数量就应该加一
根据这个定义,初始化时将目标字符串t中所以字符放到这个容器中,代表缺少t中所有的字符,即
for(auto ch : t) ++map[ch];
不过,注意上述对于容器map的操作规定没有涉及字符ch是否是目标字符串t中的字符,也就是说在规则中的ch不一定属于t。这不要紧,因为根据定义,遍历到的字符ch会执行map[ch]–,那么如果ch不在t中,说明执行后map[ch]小于0,根据小于0的定义,是说明滑动窗中富余|map[ch]|个ch。富余没关系,关键是不能缺少。
那么什么时候代表滑动窗中包含了t中所有元素呢,需要定义一个计数器,这个计数器记录着当前滑动窗口缺少t中多少个字符。可以用一个int类型的变量,初始时是t的总大小。
那么什么时候更新这个计数器呢,需要根据begin和end遍历到的字符进行适当更新
适当更新的含义是,有时候更新,有时候不更新0.0
其实是这样:)
如果通过end添加进滑动窗的字符是容器缺少的那个(由上面定义可知缺少的字符一定都是t中的字符),那么计数器减一。怎么判断是不是容器缺少的呢,可以判断map[s[end]]是否大于0
如果通过begin删掉的字符刚好是容器中既不缺少的字符,也不是富余的字符(由上面定义可知这些字符也一定都是t中的字符),那么计数器加一。同理判断依据是map[s[end]]是否等于0
另外需要注意,在进行左边减的过程中(即已经确定滑动窗中包含t中所有字符),需要不断记录最短的滑动窗的位置
代码如下
class Solution {
public:
string minWindow(string s, string t) {
vector<int> map(128, 0);
for(auto ch : t) ++map[ch];
int counter = t.size();
int begin = 0, end = 0;
int head = 0;
int len = INT_MAX;
while(end < s.size())
{
/* 右边加,当这个字符是滑动窗缺少的字符时,计数器减一 */
if(map[s[end++]]-- > 0) --counter;
while(counter == 0)
{
if(end - begin < len)
{
len = end - begin;
head = begin;
}
/* 左边减,当这个字符是滑动窗不缺也不富余的字符时,计数器加一 */
if(map[s[begin++]]++ == 0) ++counter;
}
}
return len == INT_MAX ? "" : s.substr(head, len);
}
};
本题主要的难点在需要动态改变滑动窗的大小,带来的问题是右边加左边减需要分开执行
相关文章推荐
- 每天一道LeetCode-----给定字符串s和字符数组words,在s中找到words出现的位置,words内部字符串顺序无要求
- 给定一个字符串,找到包含该字符串所有字符的最短子串
- 每天一道LeetCode-----找到一个字符串在另一个字符串出现的位置,字符串内部顺序无要求
- 每天一道LeetCode-----找到给定序列中所有和为某个值的集合或集合个数,序列中可以有/无重复项,集合元素顺序不同算不同集合等
- 每天一道LeetCode-----找到所有被某个字符包围的另一个字符
- 每天学习一算法系列(30)(给一个很长的字符串str 还有一个字符集比如{a,b,c} 找出str 里包含{a,b,c}的最短子串。要求O(n).)
- 一个字符串中包含另一个字符串所有字符的最短子串长度?——《编程之美》最短摘要的生成的简化
- 一个字符串中包含另一个字符串所有字符的最短子串
- 一个字符串中包含另一个字符串所有字符的最短子串长度?——《编程之美》最短摘要的生成的简化
- 算法题:给定一字符串,求其包含给定字符集中所有字符的最短子串
- 每天一道LeetCode-----将字符串切分,使每个子串都是回文串,计算所有可能结果和最小切分次数
- 从一个包含汉字和其他字符的字符串中截取指定字节长度的字符串,不可以出现中文乱码
- 请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。
- 每天一道算法题1 判断字符串是否是回文字符串或者是否含有回文字符子串
- 给一个很长的字符串str 还有一个字符集比如"abc" 找出str 里包含"abc"的最短子串。要求O(n)
- 每天一道LeetCode-----找到序列中第一个没有出现的正整数,要求时间复杂度是O(n),空间复杂度是O(1)
- Substring with Concatenation of All Words字符串中找到包含所有单词的子串
- 每天一道LeetCode-----将字符串切分成若干单词,使得每个单词都在给定的字典中,求出所有的切分结果
- 每天一道LeetCode-----找到1,2,...,n这n个数所有的组合,每个组合有k个元素,且元素大小递增
- 不同的取法输出顺序可以不考虑。取字符( 从标准输入读入一个由字母构成的串(不大于30个字符)。从该串中取出3个不重复的字符,求所有的取法。取出的字符,要求按字母升序排列成一个串。)