0
点赞
收藏
分享

微信扫一扫

算法题一:最小覆盖子串

月孛星君 2022-01-13 阅读 53

一、问题描述

给你两个字符串S和T,请你在S中找到包含T中全部字符的最短子串。如果没有这样一个子串,则算法返回空串;若找到这样一个子串,则可认为答案是唯一的。

二、暴力解法

1.算法

//暴力解法
string fun(string s, string t)
{
	unordered_map<char, int> need;
	unordered_map<char, int> temp;
	//遍历字符串t,记录其中每个字符出现的次数
	for (char c : t)
		need[c]++;

	int len = s.size()+1, right = 0, left = 0;
	int minLen=t.size();//字符串T的长度也是最短子串的最小长度
	for (int i = 0;i < s.size();i++)
	{
		for (int j = i + 1;j < s.size();j++)
		{
			if (j - i >= minLen)
			{//只有子串长度大于或等于T的长度,才能包含T中所有的字符
				int index = 0;
				int count = 0;
				while (index < t.size())
				{//遍历字符串t,保证子串包含t中所有字符				
					string tempS = s.substr(i, j - i);

					//使用temp记录当前子串tempS中的字符,便于和t中字符比较
					for (char c : tempS)
						temp[c]++;

					char f = t[index];
					index++;
					if (temp.count(f))					
						count++;										
					else
						break;//子串tempS不包含字符f,则当前子串无效,中段循环
					temp.clear();//清除temp中的所有元素,为保存下一子串做准备
				}

				//count == minLen表明当前子串s.substr(left,j-i)中包含t中所有字符,且当前子串长度小于上一子串长度就更新子串
				if (count == minLen && (j-i<len))
				{
					len = j - i;
					left = i;
					right = j;
					//cout << "left=" << left << "  right" << right << " len" << len << endl;
					break;//此处中断的是第二次循环,因为子串s.substr(left,j-i)长度一定小于子串s.substr(left,j+1-i)的长度
				}
			}
		}
	}

	return len == s.size() + 1 ? " " : s.substr(left, len);
}

``

2.分析

两层for循环遍历字符串s是为了不断获取子串s.substr(i,j-1),以便查看子串是否包含t中的全部字符(即第二层循环中while的功能),在所有符合条件的子串中选出长度最小的子串(若存在两个或两个以上的最小字串,则选取最前的子串最为答案)。算法的重要地方均在代码中已给出了注释,若仍有不懂的请留言谈论,我会尽快回复。
该算法思想直接简单,且使用了三层循环,时间复杂度必定大于O(N^2)。
注:字符串t中不可出现中重复字符

三、滑动窗口算法

1.算法思想

1):我们在字符串s中使用双指针中左、右指针技巧,初始化left=right=0,将索引左闭右开区间**[left,right)称为一个窗口
2):先不断地增加right指针扩大窗口
[left,right),直到窗口中的字符串符合要求(包含t中的所有字符)。
3):此时停止增加right,转而不断增加left指针缩小窗口
[left,right)**,直到窗口中的字符不再符合要求,同时每次增加left,都要更新一次结果。
4):重复第2和第3步,直到right到达字符串s的尽头。

2.算法代码

//移动窗口函数
string minWindow(string s, string t)
{
	unordered_map<char, int> need, window;

	//遍历字符串t,记录其中每个字符出现的次数
	for (char c : t)
		need[c]++;
	
	int left = 0, right = 0;
	int valid = 0;

	//记录最小覆盖字串的起始索引和长度
	int start = 0, len = s.size()+1;

	while (right < s.size())
	{
		char c = s[right];
		right++;//向右扩大窗口

		if (need.count(c))
		{
			window[c]++;

			if (window[c] == need[c])			
				valid++;//当前字符在window与need中的对应值一致,则将有效值加一				
		}

		//valid == need.size()表示在当前窗体中已经包含need中的所有字符
		while (valid == t.length())
		{//进入循环,直到窗体window中不在完全包含need中的所有字符
			int formedSize=right-left;
			if (right - left< len)
			{			
				start = left;//更新窗口起始位
				len = right - left;//更新窗口大小
			}

			char c = s[left];//提取当前窗口第一个字符
			left++;//窗口左端右移

			if (need.count(c))
			{
				if (window[c]==need[c])
					valid--;//当前字符在window与need中的对应值一致,则将有效值减一
				window[c]--;//当前字符在need(t)中存在,便将window中的对应值减一
			}
		}
	}

	//cout << endl;
	//cout << "start=" << start <<"  len="<< len<< endl;
	return len == s.size()+1 ? "" : s.substr(start, len);
}

3.简单分析

算法中只在while (right < s.size())中嵌套了while (valid == t.length())一个循环,属于两层循环。算法的时间复杂度明显比暴力解法的时间复杂度小。
注:该滑动窗口算法是本人在<<labuladong的算法小炒>>一书的1.7.1节中学习所得,主要代码与书中基本一致,只修改了小部分以及添加了一部分的注释。

举报

相关推荐

0 条评论