题目描述1
给定一个字符串 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" 的字母异位词。
我的想法(失败)
字符串的匹配与否,最先想到的是暴力循环,一共嵌套了4个循环,若s的长度为m,p的长度为n,则时间复杂度为 O ( m n 3 ) O(mn^3) O(mn3)。
遍历s到其剩余字符数少于p的字符数时可停止,每次遍历在s中从当前位置i延选与p长度相同的区间,开始一一比较,当区间内字符与p中可一一对应时,则将当前位置i加入返回列表。在区间比较中,我设置了一个长度为p的长度的位置数组,来存储已匹配的字符位置 ,当下一次比较字符与这次相同时就可跳过该位置,避免重复。
但因该方法复杂度过高,在34/36实例超时挂掉。
//结果失败,倒在了34/36实例,超时
class Solution {
public List<Integer> findAnagrams(String s, String p) {
List<Integer> fa = new ArrayList<>(); //申请一个获得最终存储索引的返回列表
int i = 0; //s索引初始化为0
for(i = 0; i < s.length() - p.length() + 1; i++) //当s中剩余字符数少于p字符数时停止
{
int count = 0; //当前区间s与p的匹配数
int[] x = new int[p.length()]; //记录p中已于s匹配的位置,使得下次查找跳过该位置,不重复
for(int c = 0; c < p.length(); c++)
{
x[c] = -1; //初始为-1
}
for(int j = 0; j < p.length(); j++) //从s的当前索引i遍历p的长度,与p比较
{
for(int k = 0; k < p.length(); k++) //让p从头与s[i+j]一一比较
{
if(s.charAt(i+j) == p.charAt(k)) //当前s与p匹配时
{
x[j] = k; //记录已匹配位置
int count1 = 0; //记录当前字符位置相异数(避免重复值的干扰)
for(int b = 0; b < p.length(); b++)
{
if(k != x[b]) //若当前匹配位置没有出现过
{
count1++;
}
}
if(count1 == p.length()-1) //若当前字符匹配且与之前位置不重复,则count1值应为p的长度减1
{
count++;
break;
}
}
}
}
if(count == p.length()) //s与p的匹配数相同时,将索引i加入返回列表中
{
fa.add(i);
}
}
return fa;
}
}
别人的想法2
用滑动窗口思想(链接中有图解)
- 计算字符串p中各个元素出现的次数,用数组记录
- 用变量start表示窗口的起始位置,变量end表示窗口的结束位置,区间[start,end]用于记录当前窗口中的元素。
扩大窗口右侧边界的条件是:字符串s还有剩余元素未考察且窗口[start,end]内的字符长度小于字符串p的长度;
缩小窗口左侧边界的条件是:窗口[start,end]内字符的长度等于字符串p的长度。 - 窗口[start,end]内字符的长度等于字符串p的长度时,接着要做的就是判断窗口内的字符串是不是字符串p的字母异位词,比较窗口内各元素的出现次数和字符串p中各元素的出现次数是否一样,一样就对,不一样就错(每个字母出现次数相同,只是顺序不同)。
很妙,时间复杂度为 O ( m n ) O(mn) O(mn)。
public List<Integer> findAnagrams(String s, String p) {
List<Integer> resultList = new ArrayList<>();
// 计算字符串p中各元素的出现次数
int[] pFreq = new int[26];
for(int i = 0; i < p.length(); i++) {
pFreq[p.charAt(i)-'a']++;
}
// 窗口区间为[start,end]
int start = 0, end = -1;
while (start <s.length()) {
// 还有剩余元素未考察,且窗口内字符串长度小于字符串p的长度
// 则扩大窗口右侧边界
if (end+1 < s.length() && end-start+1 <p.length()) {
end++;
}else {
// 右侧边界不能继续扩大或窗口内字符串长度等于字符串p的长度
// 则缩小左侧边界
start++;
}
// 当窗口内字符串长度等于字符串p的长度时,则判断其是不是字符串p的字母异位词子串
if (end-start+1 == p.length() && isAnagrams(s.substring(start,end+1), pFreq)) {
resultList.add(start);
}
}
return resultList;
}
// 判断当前子串是不是字符串p的字母异位词
private boolean isAnagrams(String window, int[] pFreq) {
// 计算窗口内字符串各元素的出现次数
int[] windowFreq = new int[26];
for(int i = 0; i < window.length(); i++) {
windowFreq[window.charAt(i)-'a']++;
}
// 比较窗口内各元素的出现次数和字符串p中各元素的出现次数是否一样
// 如果都一样,则说明窗口内的字符串是字符串p的字母异位词子串
// 如果不一样,则说明不是其子串
for(int j = 0; j < 26; j++) {
if (windowFreq[j] != pFreq[j]) {
return false;
}
}
return true;
}
参考链接
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/find-all-anagrams-in-a-string
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 ↩︎作者:hardcore-aryabhata
链接:https://leetcode-cn.com/problems/find-all-anagrams-in-a-string/solution/hua-dong-chuang-kou-438-zhao-dao-zi-fu-c-ut38/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 ↩︎