给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。
字符串只包含小写英文字母,并且字符串 s 和 p 的长度都不超过 20100。
说明:
字母异位词指字母相同,但排列不同的字符串。
不考虑答案输出的顺序。
示例 1:
输入:
s: "cbaebabacd" p: "abc"
输出:
[0, 6]
解释:
起始索引等于 0 的子串是 "cba", 它是 "abc" 的字母异位词。
起始索引等于 6 的子串是 "bac", 它是 "abc" 的字母异位词。
示例 2:
输入:
s: "abab" p: "ab"
输出:
[0, 1, 2]
解释:
起始索引等于 0 的子串是 "ab", 它是 "ab" 的字母异位词。
起始索引等于 1 的子串是 "ba", 它是 "ab" 的字母异位词。
起始索引等于 2 的子串是 "ab", 它是 "ab" 的字母异位词。
一、思路
(一)一次比较的字符串匹配方法
之前想过一些方法,都有各种各样的缺点,比如:
(1)从字符串s中取一个长度与p一样的子串,对两者进行排序,判断是否相等
(2)使用映射法:将每个字符映射到某个数字,并且要求无论怎么相加,都不会出现两个不同(不包括异构)的字符串,结果一致的情况
实际上
(1)是可以通过部分用例的,但是会超时,其根源在于s中的某些字符进行了多次重复匹配,导致时间复杂度过高
(2)的问题在于如何求解这样的映射
一次比较的字符串匹配方法:可以有效解决字符的重复匹配问题。
我们首先设置一个数组dp(这个方法借鉴了一部分动态规划的思想):
dp[i]:表示 从字符串s的第i个字符开始算起,往前dp[i]个字符,都能在字符串p中找到。
我们对字符串s中的每一个字符进行逐个匹配:
- 当发现字符s[i]能够在p中找到时,认为匹配成功,并且将字符s[i]从p中删除,并且匹配长度+1:dp[i]=dp[i-1]+1
- 当发现字符s[i]不能在p中找到时,认为匹配失败,那么失败的原因是什么呢?
其一:字符s[i]确实不在p中,此时长度变为0,要重新开始匹配
其二:字符s[i]在p中,但是因为之前的删除操作,使得p中的s[i]字符被删除了;
这种情况下,我们可以从dp[i-1]入手:
(1)从 i − d p [ i − 1 ] i-dp[i-1] i−dp[i−1]位置开始,直到 i − 1 i-1 i−1位置,都是被删除的p中的字符,从中离开始位置最近的相同字符s[j] == s[i]
(2)新的匹配长度从这里开始,然后再将j之前的字符串补到p上面
C++代码:
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
// dp[i]:表示 从字符串s的第i个字符开始算起,往前dp[i]个字符,都能在字符串p中找到
vector<int> ans;
vector<int> dp(s.size(), 0);
if(p.size() > s.size())
return ans;
int p_len = p.size();
// sort(p.begin(), p.end());
string p1 = p;
if(isMatch(p, s[0]))
dp[0]++;
if(dp[0] == p_len){
int num = 0;
ans.push_back(num);
p = p + s[num];
}
for(int i=1; i < s.size(); i++){
if(isMatch(p, s[i])){
dp[i] = (dp[i - 1] == p_len) ? (p_len) : (dp[i - 1] + 1);
if(dp[i] == p_len){
int num = i - p_len + 1;
ans.push_back(num);
p = p + s[num];
}
}
else{
// 不匹配,两种可能:
//(1) s[i]在字符串p中,因为之前的操作,去掉了
//(2) s[i]不在字符串中
if(dp[i - 1] != 0){
int begin = i - dp[i - 1];
int j = begin;
// 从被删除的字符串中,从前往后寻找第一个与s[i]相等的字符
while(j < i && s[j] != s[i])
j++;
if(j == i){ // 没有找到,s[i]不存在于p中
p = p1;
dp[i] = 0;
}
else{ // 找到了,从s[j]开始起的字符串直到s[i],都能存在于p
dp[i] = i - j;
if(dp[i - 1] != p_len)
p = p + s.substr(begin, j - begin);
}
}
else{
p = p1;
dp[i] = 0;
}
}
}
return ans;
}
// 判断是否匹配,匹配则删除
bool isMatch(string& s, char c){
for(int i=0; i < s.size(); i++){
if(s[i] == c){
s.erase(s.begin() + i, s.begin() + i + 1);
return true;
}
}
return false;
}
};