题目:最小覆盖子串
描述:给出两个字符串S和T,要求在O(n)的时间复杂度内在S中找出最短的包含T中所有字符的子串。
例如:S="XDOYEZODEYXNZ",T="XYZ"找出的最短子串为"YXNZ".
注意:如果S中没有包含T中所有字符的子串,返回空字符串“”;
满足条件的子串可能有很多,但是题目保证满足条件的最短的子串唯一。
示例1:输入:"XDOYEZODEYXNZ","XYZ",返回值:"YXNZ"
解法一:
思路分析:只读题目,想到需要采用数据结构中的KMP算法,但是当看到例如后面的分析,立马停止了这种想法,在这道题当中,选择map关联式容器的方法进行分析,将字符与出现的次数进行关联。使用两个指针分别做窗口的左右边界,使有边界一直往后滑动,直到窗口中的字符子串覆盖t串后,判断左边界是否有多余,记录此时的窗口长度、起始下标,实时更新最短长度,最后尝试着移动左边界,重复右边界持续扩大的操作,一直循环执行。
实例分析:"XDOYEZODEYXNZ","XYZ",返回值:"YXNZ"
S | X | D | O | Y | E | Z | O | D | E | Y | X | N | Z |
首先设置两个指针,左指针和右指针,左指针不变,循环有指针到包含T中所有的字符 | |||||||||||||
T | X | Y | Z |
|
|
|
|
|
|
|
|
|
|
| 1 |
|
| 1 |
| 1 |
|
|
|
|
|
|
|
| LEFT |
|
|
|
| RIGHT |
|
|
|
|
|
|
|
该窗口正好包含T中所有元素,此时可求出一组长度为6的序列,移动LEFT到不包含T为止,再进行RIGHT的寻找 | |||||||||||||
|
|
|
| 1 |
| 1 |
|
|
| 1 | 1 |
|
|
|
| LEFT |
|
|
|
|
|
|
|
|
|
|
|
输出这一组的长度为10,不进行更新,一直循环判断到…… | |||||||||||||
|
|
|
|
|
|
|
|
|
| 1 | 1 |
| 1 |
|
|
|
|
|
|
|
|
|
| LEFT |
|
| RIGHT |
输出最终的子序列 | |||||||||||||
"YXNZ" |
class Solution { public: unordered_map <char, int> ori;//t字符的数量 unordered_map <char, int> cnt;//当前区间包含的t中字符的数量 bool check(){//检查现有区间是否已经包含目的串 for (const auto &p: ori) { if (cnt[p.first] < p.second){ return false; } } return true; } string minWindow(string s, string t){ for (const auto &c: t){//初始化ori,t中字符及对应数量,X88,Y89,Z90 ++ori[c]; } int l = 0, r = -1; int len = INT_MAX, ansL = -1, ansR = -1; while (r < int(s.size())){//r初始值-1,与size_t无法比较 if (ori.find(s[++r]) != ori.end()){//s中找到一个t中的字符 ++cnt[s[r]]; } while (check()){ if (r - l + 1 < len){ //更新答案 len = r - l + 1; ansL = l; } if (ori.find(s[l]) != ori.end()){ //左指针右移 --cnt[s[l]]; } ++l; } } return ansL == -1 ? string() : s.substr(ansL, len); } };
解法二:
思路分析:我们可以分配一个大小为256的数组充当hashmap的作用,记录T中字母出现的次数,具体首先为将T中所有字符出现的次数保存在td数组中,遍历整个S字符串,开始设定两个值,分别为start表示当前字符串的起点,i表示当前字符串的终点,用found表示当前字符串中包含T中字符的数目,如果found = T.length()则表明当前字符串包含了T中全部的字符串,进入下一步,将start后移,取出start前面多余的元素,达到最小的目标。通过不断判断得出历史最小的子串,将当前子串的长度,起始点,结束点进行记录,最后输出。
import java.util.*; public class Solution { /** * * @param S string字符串 * @param T string字符串 * @return string字符串 */ public String minWindow (String S, String T) { // write code here if(S == null || S.length() == 0)//特殊情况,返回空字符串 return ""; if(T == null || T.length() == 0) return ""; int[] td = new int[256];//设置td,保存T中所有字符出现的次数 for(char tc : T.toCharArray())//字符串转换为字符数组 td[tc]++; int[] sd = new int[256]; int slen = S.length(); int start = 0,first = -1,end = 0;//距离标志 int found = 0;//在S中发现T元素的数目 for(int i = 0;i < S.length();i++){ sd[S.charAt(i)]++;//charAt(i)为第i个字符在字符串S中所占的位置 if(sd[S.charAt(i)] <= td[S.charAt(i)]){//计算S的位置是否与T的位置相等 found++; } if(found == T.length()){//满足条件 while(start <= i && sd[S.charAt(start)] > td[S.charAt(start)]){ sd[S.charAt(start)]--; start++; } //如果比当前最小子串小,则更新 if(i + 1 - start <= slen){ slen = i + 1 - start; first = start; end = i+1; } sd[S.charAt(start)]--; start++; found--; } } if(first == -1){ return ""; } else{ return S.substring(first, end);//返回最终子串的范围 } } }
在该程序中遍历S中的每一个元素进行判断,并且还需要开辟内存空间进行子串的存储,其时间复杂度为O(N),设字符集的大小为N,则空间复杂度为O(N)。