LeetCode Substring with Concatenation of All Words

本文深入探讨了字符串匹配问题的多种算法实现,包括初学者易犯错误的解决方案、高级优化技巧和直观的暴力算法。通过对比不同算法的时间效率和逻辑清晰度,揭示了编程中结构设计与思维缜密的重要性。特别强调了如何通过引入map数据结构、改进位置标记和使用窗口滑动思想来提高算法性能。文章最后分析了为何某些算法能够显著提升运行速度,并提出了一个更为高效的优化策略,使读者对字符串匹配问题有了更全面的理解和实践能力。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一看这道题就知道不是一个简单的题,不想一下子就放弃,就写了写,第一个的想法是找每个字符串出现的位置,然后判断位置是否连续(既包含所有的字符串),但是没有用map,也没有记录每个单词出现的次数,结构之混乱,思维之复杂,弄了好久调通了,时间限制。烂代码贴上也无妨,不要学就是了。

vector<int> findSubstring(string S, vector<string> &L) {
	int lenL = L.size();
	int lenstr;
	vector<int> ret;
	if (lenL==0)
		return ret;
	else
		lenstr = L[0].size();
	int *pos = new int[lenL];
	memset(pos,0,sizeof(int)*lenL);
	vector<int> st;
	bool flag =true;
	while(flag)
	{
		for (int i=0;i<lenL;i++)
		{
			bool flag2 = true;
			while(flag2)
			{
				pos[i] = S.find(L[i],pos[i]);// return -1 when doesn't exist
				if (pos[i] == -1)
				{
					flag = false;
					break;
				}
				flag2 = false;
				for (int j=0;j<i;j++)
				{
					if(pos[j]-lenstr == pos[i])
					{
						pos[i] += lenstr;
						flag2 = true;
					}
				}
			}
			if (flag == false)
				break;
			st.push_back(pos[i]);
			pos[i] += lenstr;
		}
		if (flag == false)
			break;
		//vector<int> stcopy(st);
		sort(st.begin(),st.end());
		int i;
		for (i=1;i<st.size();i++)
		{
			if (st[i-1]+lenstr != st[i])
			{
				break;
			}
		}
		if (i==st.size())
		{
			ret.push_back(st[0]);
			st.clear();
		}
		else
		{
			if (i>0&&st[i] >= st[i-1]+lenstr)
			{
				for (int j=0;j<lenL;j++)
				{
					if (pos[j]>=st[i])
					{
						pos[j] -= lenstr;
					}
				}
			}
			else
			{
				int min=1000,k=0;
				for (int j=0;j<lenL;j++)//find the least p
				{
					if (min>pos[j])
					{
						k = j;
						min = pos[j];
					}
				}
				for (int j=0;j<lenL;j++)
				{
					if (pos[j]>pos[k])
					{
						pos[j] -= lenstr;
					}
				}
			}
			st.clear();
		}
	}
	delete[] pos;
	return ret;
}
第二次想法,没有用find,用了map记录次数,不断判断下一个字符是不是给定的字符串数组里面的,然后一次移动一个位置。还是时间限制。感觉和正确的暴力算法没差多少,怎么就通不过呢?代码如下:

vector<int> findSubstring1(string S, vector<string> &L) {
	int lenL = L.size();
	int lenstr;
	vector<int> ret;
	if (lenL==0)
		return ret;
	else
		lenstr = L[0].size();
	map<string,int> mpo;
	int allsum = lenstr*lenL;
	if (allsum>S.length())
	{
		return ret;
	}
	for (int i=0;i<lenL;i++)
	{
		mpo[L[i]]++;
	}
	for (int i=0;i<S.length();i++)
	{
		map<string,int> mp(mpo);
		int j=i;
		bool flag = true;
		while(j<S.length())
		{
			map<string,int>::iterator iter = mp.begin();
			int pos = -1;
			while (iter != mp.end())
			{
				if(S.substr(j,lenstr) ==iter->first)
				{
					if (mp[iter->first]>0)
					{
						j += lenstr;
						mp[iter->first]--;
						
						if (j-i == allsum)
						{
							ret.push_back(i);
							i = i + lenstr - 1;//j-1;//i += sum-1;//here,i move 
							flag = false;
							break;
						}
					}
					else
					{
						flag = false;
						break;
					}
					iter = mp.begin();
				}
				else
				{
					iter++;
				}
			}
			if (iter == mp.end()||flag == false)
				break;
		}
		
	}
	return ret;
}
下面的两个算法,一个比一个牛逼,真是见识了。下面的暴力算法,思维之缜密,结构之清晰,令我佩服的五体投地。而且这题最让我感觉头疼的就是程序的结构,逻辑之间的关系,水平高低一下子就看出来了。同样的想法,能工整清晰的实现出来就是不一般的。有两点和上面的程序是不一样的,一是它用另一个map来记录找到的次数,来判断是不是超过了需要的字符串,二是他用j从零开始,只是作为个数,不作为索引。这样可以用j是否到个数来判断是否找到结果。但整体上比我的程序清晰多了。真是简单、粗暴、美!如下:

vector<int> findSubstring3(string S, vector<string> &L) {  //1632ms
	// Note: The Solution object is instantiated only once.  
	map<string,int> words;  
	map<string,int> cur;  
	int wordNum = L.size();  
	for(int i = 0; i < wordNum; i++)  
		words[L[i]]++;  
	int wordLen = L[0].size();  
	vector<int> res;  
	//if(S.size() < wordLen*wordNum)return res;  
	for(int i = 0; i <= (int)S.size()-wordLen*wordNum; i++)  
	{  
		cur.clear();  
		int j;  
		for(j = 0; j < wordNum; j++)  
		{  
			string word = S.substr(i+j*wordLen, wordLen);  
			if(words.find(word) == words.end())  
				break;  
			cur[word]++;  
			if(cur[word]>words[word])  
				break;  
		}  
		if(j == wordNum)  
			res.push_back(i);  
	}  
	return res;  
}  
下面的想法说是来自leetCode上的讨论,思路也很清晰,明了。我在这看的 http://blog.youkuaiyun.com/kenden23/article/details/17054283 思路也是:先记录位置(和我最开始的想法一样),然后判断位置是否连续。不过比我那高级多了,先是记录位置时就已经区分了不同的单词位置标记不一样;然后用need和found来表示是否找到,找到多少,差多少等;然后用了窗口的思想,不断的像后移动窗口,一次一个固定长度(之前还要确定步长)。看倒是能看明白,写我写不出来,一年年后我能写出这样的代码就满足了。

vector<int> findSubstring(string S, vector<string> &L) {  //226ms
	// Start typing your C/C++ solution below  
	// DO NOT write int main() function  
	int m = S.size(), n = L.size(), len = L[0].size();  
	map<string, int> ids;  

	vector<int> need(n, 0);  
	for (int i = 0; i < n; ++i) {  
		if (!ids.count(L[i])) ids[L[i]] = i;  
		need[ids[L[i]]]++;  
	}  

	vector<int> s(m, -1);  
	for (int i = 0; i < m - len + 1; ++i) {  
		string key = S.substr(i, len);  
		s[i] = ids.count(key) ? ids[key] : -1;  
	}  

	vector<int> ret;  
	for (int offset = 0; offset < len; ++offset) {  
		vector<int> found(n, 0);  
		int count = 0, begin = offset;  
		for (int i = offset; i < m - len + 1; i += len) {  
			if (s[i] < 0) {  
				// recount  
				begin = i + len;  
				count = 0;  
				found.clear();  
				found.resize(n, 0);  
			} else {  
				int id = s[i];  
				found[id]++;  
				if (need[id] && found[id] <= need[id])  
					count++;  

				if (count == n)  
					ret.push_back(begin);  

				// move current window  
				if ((i - begin) / len + 1 == n) {  
					id = s[begin];  
					if (need[id] && found[id] == need[id])  
						count--;  
					found[id]--;  
					begin += len;  
				}  
			}  
		}  
	}  
	return ret;  
}  
还有一个方法,比上面的方法更快一点,算是改进了,不过思路没有上面的清晰。下面的之所以快一点,是因为在找到的字符串数量超过需要的时候,上面的算法直接让i走到下一个点(即i加一个步长),而下面的算法却增加了一步,寻找最后的超过数量的字符串位置,i修正为第一个这个字符串(超过数量的那个)出现的位置,这样i一次走的更长了。这算法算是完美了吧,但是对艺术的追求是没止境的,当然了,也没准有更好的,不过对于我这样的菜鸟能看明白,并且时隔多日还能记得一点点就算不错了,也算是对得起大牛们的杰作。

vector<int> findSubstring5(string S, vector<string> &L) 
{
	int sLen = S.length();
	int lLen = L.size();
	if (lLen < 1) return vector<int>();
	int len = L[0].length();

	vector<int> res;
	map<string, int> LMap;
	map<string, int> countMap;

	string index;
	for (int i=0;i<lLen;i++)
		LMap[L[i]]++;

	//熟记这个思维,巧妙地分段
	for (int d = 0; d < len; d++)
	{
		//注意:是i=d不是i=0;
		for (int i = d; i <= sLen-lLen*len; i += len)
		{
			int start = i;
			index = S.substr(i,len);
			//注意:后面的是i<=sLen-len不是sLen-lLen*len
			while (LMap.count(index) > 0 && i <= sLen-len)
			{
				countMap[index]++;
				//这里就不能用map的count函数了,因为count只会返回0或1
				//一定要熟悉STL函数的各种用法,否则会出错。
				//这个函数把握不好,结果浪费大量的时间。
				//本题卡的最长时间的是这个小小的函数和程序结构开始没写好
				if (countMap[index] > LMap[index])
				{
					string sTemp = S.substr(start,len);
					while (sTemp != index)
					{
						start += len;
						sTemp = S.substr(start,len);
						//每前进一步,减少一个相等元素
						countMap[sTemp]--;
					}
					countMap[index]--;
					start += len;
				}

				i+=len;
				if ((i-start)/len == lLen)
				{
					res.push_back(start);
					string sTemp = S.substr(start, len);
					countMap[sTemp]--;
					start+=len;
				}

				index = S.substr(i,len);
			}
			countMap.clear();
		}
	}
	return res;
}
最后提出一个问题,也是最重要的问题,就是:为什么后面的方法会比前面的快那么多呢?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值