您的位置:首页 > 其它

每天一道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
相关文章推荐