题目描述
给定一个字符串s
和一个字符串数组words
。words
中所有字符串长度相同。
s
中的 串联子串 是指一个包含words
中所有字符串以任意顺序排列连接起来的子串。
例如,如果words = ["ab","cd","ef"]
, 那么"abcdef"
,"abefcd"
,"cdabef"
,"cdefab"
,"efabcd"
,和"efcdab"
都是串联子串。"acdbef"
不是串联子串,因为他不是任何words
排列的连接。
返回所有串联子串在s
中的开始索引。你可以以 任意顺序 返回答案。
解题思路
首先不妨假定一下字符串s
的长度为n
,单词列表words
长度为l
,以及每个单词的长度为w
。由于子串由words
中的单词以任意顺序排列构成,所以长度是确定的,即w×l。那么我们就可以对字串开头进行枚举,而后针对每一个子串,分别统计其中的单词是不是words
的一个排列。这样的算法枚举子串开头的时间复杂度是O(n),循环内层的判定的时间复杂度是O(l),算法总的复杂度是O(nl).
有一个明显的可以优化的点在于,假设子串起始下标为st
,则当我们检查完[st, st+wl-1]
的子串后,我们下一个检查的子串是[st+1, st+wl]
。可以看到,检查第二个子串的过程中,其实一大部分是我们在前一次检查中已经扫描过的值,唯一的变化在于子串起始位置和末了位置产生了一定的偏移。因此,可以考虑滑动窗口的方法。
但在此题中,直接使用滑动窗口似乎没那么好用。究其原因在于当子串左右端点移动时只移动了一个字符,而我们维护的却是以word
为单元的出现次数。因此,要使用滑动窗口方法,我们需要将每w
个字符视作一个单元;子串端点移动时,也以w
个字符为步长进行移动。
当然,由于滑动窗口的出发点是下标为0的位置,这样做会使得我们的子串起始点仅能位于w
的整数倍处。一个解决方法是我们枚举所有可能的起始点,即模w的剩余类,这样,子串就可以取到所有可能起始点了。
这般,我们枚举可能起始点的时间复杂度为O(w)(即0到w−1),每一次枚举后使用滑动窗口来判断能否构成满足要求的子串,时间复杂度为O(n/w)(因为我们将w
个字符视作了一个单元,因此总长度为n/w)。整个算法时间复杂度为O(n)。
代码(C++)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| class Solution { void reset_map(unordered_map<string, int>& mp){ for(auto &kv: mp){ kv.second = 0; } return; }
void findSubstringAt(string& s, vector<string>& words, int st, vector<int>& ans, unordered_map<string, int>& target, unordered_map<string, int>& mp){ int w = words[0].length(), l = words.size(), n = s.length(); int cur = st; while(cur <= n - w){ string substr = string(s, cur, w); auto it = mp.find(substr); if(it == mp.end()){ st = cur + w; reset_map(mp); }else{ mp[substr] ++; while(mp[substr] > target[substr]){ mp[string(s, st, w)] --; st += w; } if(cur - st == w*(l-1)){ ans.push_back(st); } }
cur += w; }
return; } public: vector<int> findSubstring(string s, vector<string>& words) { int n = s.length(), l = words.size(), w = words[0].length(); vector<int> ans; unordered_map<string, int> target; unordered_map<string, int> mp; for(auto word: words){ mp[word] = 0; } for(auto word: words){ if(target.find(word) != target.end()) target[word] ++; else target[word] = 1; }
for(int i=0; i<w; ++i){ reset_map(mp); findSubstringAt(s, words, i, ans, target, mp); }
return ans;
} };
|
时空复杂度