Given a string s and a non-empty string p, find all the start indices of p’s anagrams in s.
Strings consists of lowercase English letters only and the length of both strings s and p will not be larger than 20,100.
The order of output does not matter.
Example 1:
Input:
s: "cbaebabacd" p: "abc"
Output:
[0, 6]
Explanation:
The substring with start index = 0 is "cba", which is an anagram of "abc".
The substring with start index = 6 is "bac", which is an anagram of "abc".
Example 2:
Input:
s: "abab" p: "ab"
Output:
[0, 1, 2]
Explanation:
The substring with start index = 0 is "ab", which is an anagram of "ab".
The substring with start index = 1 is "ba", which is an anagram of "ab".
The substring with start index = 2 is "ab", which is an anagram of "ab".
给定一个字符串 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" 的字母异位词。
解法一:
题目给了两个字符串,要求我们从 s 中找到所有是 p 的异位词的子串。所谓异位词就是字符串字符种类均相同但顺序可以不同的两个子串,那么首先就要先统计字符串 p 中的字符出现的次数,然后依次从字符串 s 中取出 p 字符串长度的子串进行比较,如果 s 子串中的每一个字符都在 p 字符串中出现,那么就记录当前子串的起始位置,否则 break 当前循环。
public List<Integer> findAnagrams(String s, String p) {
List<Integer> result = new ArrayList<>();
if (s == null || s.length() == 0 || p == null || p.length() == 0)
return result;
int[] hash = new int[256];
for (char i : p.toCharArray()) {
hash[i]++;
}
int ns = s.length(), np = p.length();
int i = 0;
boolean success;
while (i < ns - np + 1) {
success = true;
int[] tmp = hash.clone();
for (int j = i ; j < i + np ; j ++) {
if (--tmp[s.charAt(j)] < 0) {
success = false;
break;
}
}
if (success) {
result.add(i);
}
++i;
}
return result;
}
解法二:
从解法一可以看出,内层循环不断在对一个 p 长度大小的子串进行遍历,连起来就是一个滑动窗口。
这个解法是参考LeetCode一大神的分享,代码很简洁,使用到了 hash 数组和滑动窗口。
public List<Integer> findAnagrams(String s, String p) {
List<Integer> result = new ArrayList<>();
if (s == null || s.length() == 0 || p == null || p.length() == 0)
return result;
int[] hash = new int[256];
for (char i : p.toCharArray()) {
hash[i]++;
}
int left = 0, right = 0, count = p.length(); // 差异度
while (right < s.length()) {
// 窗口右移;相应的hash值减小;如果这个位置的Hash值是正的,
// 表示当前字符包含在 p 字符串中,所以差异度减一
if (hash[s.charAt(right)] > 0) {
count--;
}
hash[s.charAt(right)] --;
right ++;
// 差异度为0,加入结果集合中
if (count == 0)
result.add(left);
// 如果当窗口大小一定的时候即窗口大小和需要比较的字符串大小一致的时候,
// 将窗口左边的指针向右边移动,移动的同时左边的字符计数因为在第一个if的地方hash值减小过,
// 所以需要执行对应恢复操作,即:hash值增加,count计数值增加。
if (right - left == p.length()) {
if (hash[s.charAt(left)] >= 0) {
count++;
}
hash[s.charAt(left)] ++;
left ++;
}
}
return result;
}
首先就是定义的 hash 数组长度是 256,因为 ascii 码的长度是 256 位的,所以每一位的索引表示一个字符的计数值。然后 count 是 s 的子串与 p 字符串相差的字符个数,即差异度。
循环右移的过程中,如果当前字符包含在 p 中,那么差异度减一,同时维护 hash 数组对应的值减一。如果差异度为 0 了,也就是子串符合要求了,则将滑动窗口的左边界加入结果集。当滑动窗口的大小等于 p 的长度时,就需要将滑动窗口的左边界右移一位,并维护 hash 数组的值加一,同时判断左边界当前的值是否大于等于 0 :因为如果是小于 0,也就是第一个 if 语句中减一之后变为负数的,说明在 p 中不存在,差异度无须恢复;如果存在,那么把差异度恢复加一。
最后右边界到达终点,循环结束,返回记录下的起始索引即可。
更加简单的写法:
public List<Integer> findAnagrams(String s, String p) {
List<Integer> list = new ArrayList<>();
if (s == null || s.length() == 0 || p == null || p.length() == 0) return list;
int[] hash = new int[256]; // hash 数组
// 保存字符的计数值
for (char c : p.toCharArray()) {
hash[c]++;
}
int left = 0, right = 0, count = p.length();
while (right < s.length()) {
// 每次右移都将对应的 hash 值减一
if (hash[s.charAt(right++)]-- >= 1) count--;
// 差异度为0,加入结果集合中
if (count == 0) list.add(left);
// 如果当窗口大小一定的时候即窗口大小和需要比较的字符串大小一致的时候,
// 将窗口左边的指针向右边移动,移动的同时左边的字符计数因为在第一个if的地方hash值减小过,
// 所以需要执行对应恢复操作,即:hash值增加,count计数值增加。
if (right - left == p.length() && hash[s.charAt(left++)]++ >= 0) count++;
}
return list;
}
简单写法我觉得不太容易理解,整个过程自己调试了很多遍,希望大家多多指点。:)