一、问题描述:
给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。
异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。
二、解题思路:
- 如何判断s的子串与p是异位词??
异位词是由相同的字母按照不同顺序组成。我们定义两个数组cur、index,用来存储字符串s的子串与字符串p中对应字符(a~z)出现的次数!再比较两个数组内容是否一致,即可判断。 - s的子串以一个滑动窗口来表示。定义左右指针left、right,两个索引之间的子串长度与字符串p长度一致,用作与字符串p进行比较。
- 具体步骤:
①创建一个大小为 26 的整型数组 index 和 cur,用于统计字符串 p 和当前窗口内的字符出现次数。
②遍历字符串 p,将 p 中每个字符出现的次数存储在 index 数组中,索引对应字符的 ASCII 码减去 ‘a’ 的值。
③使用滑动窗口的方式遍历字符串 s,维护两个指针 left 和 right 分别表示窗口的起始和结束位置。
④在每一步迭代中,判断当前窗口内的字符出现次数是否与字符串 p 的字符出现次数一致,如果一致则将左指针位置加入结果集合 res。
⑤移动左指针 left 和右指针 right,更新窗口内字符的出现次数。更新方式是先将左指针位置的字符出现次数减一,然后将右指针位置的字符出现次数加一。
⑥循环直到右指针 right 遍历完整个字符串 s,返回结果集合 res。
三、代码示例:
- 滑动窗口
//时间复杂度 O(n)
class Solution {
public List<Integer> findAnagrams(String s, String p) {
List<Integer> res = new ArrayList<>();
int[] index = new int[26];
int[] cur = new int[26];
if(s.length() < p.length()) return res; //字符串s长度的小于字符串p,返回为空
for(int i = 0; i < p.length(); i++) {
index[p.charAt(i) - 'a']++; //记录p中字母的词频
cur[s.charAt(i) - 'a']++; //记录s中前p.length()个字母的词频
}
int left = 0, right = p.length() - 1; //左右指针初始化,从第一个与p长度一致的子串开始查找
while(right < s.length()) {
if(Arrays.equals(index, cur)) res.add(left); //是异位词,存入结果集
cur[s.charAt(left) - 'a']--; //更新窗口中的值,删掉左指针指向的元素
left++; //左右指针均向右移动一位!
right++; //要先自增
if(right < s.length()) cur[s.charAt(right) - 'a']++; //添加右指针下一位的元素,防止右侧索引溢出
}
return res;
}
}
- 时间复杂度分析:
首先,在代码开头对字符串p进行遍历,统计p中各个字母的出现次数,时间复杂度为O(p.length())。
然后,使用滑动窗口在字符串s上进行操作,具体来说:
窗口的大小为p.length(),因此整个过程总共需要移动n = s.length() - p.length() + 1 次。
在每次移动过程中,执行的操作包括更新窗口内的字母出现次数和比较两个数组是否相等,这些操作的时间复杂度均为O(26) = O(1)。
因此,整个滑动窗口的操作的时间复杂度为O(n)。
四、补充与总结:
- Arrays.equals(arr1, arr2);
用来比较两个数组是否相等的方法(即每个位置上的元素都相等)。在该例中,Arrays.equals(index, cur) 为 true 则说明当前窗口内的字符出现次数与字符串 p 的字符出现次数完全一致,符合字母异位词的定义。 - 在使用滑动窗口时一定要先理清楚左右指针该如何移动!
- 统计字符串中字符出现次数(词频)时常用的一种方法:创建一个大小为 26 的整型数组,通过将数组的索引与字母表中的字母相对应,可以很方便地统计每个字母出现的次数。例如,索引 0 对应字母 ‘a’,索引 1 对应字母 ‘b’,以此类推。
当遍历字符串时,可以根据字符的 ASCII 码值减去 ‘a’ 的 ASCII 码值来确定该字符在数组中的索引位置,然后对相应索引位置上的元素进行加一操作,从而实现字符出现次数的统计。这样,无需使用哈希表等数据结构,节省了空间并且提高了效率!
需要注意的是,这种方法只适用于统计小写字母的出现次数。如果需要统计其他字符(包括大写字母、数字、特殊符号等),就需要相应扩大数组的大小,并对字符进行映射到数组索引的处理。