每天一道LeetCode-----在字符串s中找到最短的包含字符串t中所有字符的子串,子串中字符顺序无要求且可以有其他字符
来源:互联网 发布:java短信验证码第三方 编辑:程序博客网 时间:2024/05/18 03:01
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中找到最短的包含字符串t中所有字符的子串,子串中字符顺序无要求且可以有其他字符
- 每天一道LeetCode-----给定字符串s和字符数组words,在s中找到words出现的位置,words内部字符串顺序无要求
- 找到字符串中无重复最长的字符子串
- 每天一道LeetCode-----找到一个字符串在另一个字符串出现的位置,字符串内部顺序无要求
- Distinct Subsequences 在S中找T包含的所有字符的组合数@LeetCode
- 编写算法,求所有包含字符串S中而不包含字符串T中的字符串,构成的新串r以及r中每一个字符在S中第一次出现的位置
- 源字符串中包含目标字符串所有字符的最小子串
- [LeetCode] minimum window 包含所有字符的最小子字符串
- 如果字符串一的所有字符按其在字符串中的顺序出现在另外一个字符串二中,则字符串一称之为字符串二的子串。
- 返回字符t在字符串s中最右边出现的位置,若s中不包括t,则返回-1
- 给定字符串S和字符串T,找到S中的最小窗口,其中将包含复杂度O(n)中T中的所有字符。
- 求字符串中包含唯一字符的最长子串
- 一个字符串中包含另一个字符串所有字符的最短子串
- 找到字符串的最长无重复字符子串
- 找到字符串的最长无重复字符子串
- 40.给字符串s1、s2,在s1中找包含s2里所有字符的最小子串
- 给字符串s1、s2,在s1中找包含s2里所有字符的最小子串
- 求字符串中最长无重复字符的子串
- easyUI--局部刷新
- VC/MFC如何设置对话框背景颜色
- Elasticsearch笔记六之中文分词器及自定义分词器
- CMD命令操作MySql数据库详解
- 蚂蚁花呗提现方法及花呗怎么提现详解
- 每天一道LeetCode-----在字符串s中找到最短的包含字符串t中所有字符的子串,子串中字符顺序无要求且可以有其他字符
- python3.6+selenium+phantomJS 网页爬虫报错NoSuchElementException问题及解决方法
- Spring Cloud 配置文件切换(profiles的应用)
- 扫描二维码
- 使用java BufferedWriter写txt文本
- oncmdb部署和密码修改
- Problem B: Sequence Problem (II) : Array Practice
- 如何禁止电脑文件被复制
- java NIO selector ServerSocketChannel 例子