- 找到字符串中所有字母异位词
给定一个字符串 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” 的字母异位词。
解题思路
滑动窗口
思路:这个题目如果有滑动窗口的想法,很容易想到。一个长度等于p的窗口在s上滑动。难点在于怎么判断窗口字符串和p是否是异味子串。并且移动时怎么处理元素。想到用map或者set可以记录字符是否存在,但是这里可能会有重复字符。
灵感来自java对象的回收算法,对于一个对象被很多地方引用。每个引用计数+1,当消除一个引用时,垃圾回收期不会立即回收该对象。而是将对象的计数器的值-1,当值等于0时才回收。这里也计数,当窗口右移时,第一次添加的元素值为1,否则值+1.
窗口左边废弃元素,同理,大于1时-1,等于1时直接移除。
做法:用两个指针代表窗口的左右两端,间隔是p的长度。用map保存元素和出现的次数。向右滑动窗口,每滑动一次将新元素添加到map中,同时左边过去的元素从map中移除。
判断当前子串和p是否是异位字符串。当滑动到最右边时结束。
注意:不能用比较两个Integer对象的大小。因为他们不是int这种基础数据类型。他们是对象,这是判断对象是否是同一个。即使值一样,还是显示不相同。因为这是两个对象。在Integer中有个IntegerCache[],里面存储了-128~127之间的值,只有里面的值可以用比较大小。
时间复杂度:O(m*n),m是字符串s的长度,n是字符串n的长度。因为相当于向右遍历一遍s,每移动一个元素,都要循环判断窗口子串即长度p的。空间复杂度:O(n)
代码
class Solution {
public static List<Integer> findAnagrams(String s, String p) {
List<Integer> list = new ArrayList<>();
//对于这种特殊情况,不要返回null,要返回空要求返回值。否则oj会判错。
if (s.length() < p.length()) return list;
//用两个map记录各个字符出现次数
HashMap<Character, Integer> map1 = new HashMap<>();
HashMap<Character, Integer> map2 = new HashMap<>();
//初始化,将p和s字符串前p长度个字符分别添加到map中
for (int i = 0; i < p.length(); i++) {
//这里为了代码简洁抽取成一个方法。其实没必要,这样做会让oj中程序运行时间大大增加,这里不抽取运行时间56ms,抽取后运行时间2604ms。
// 即不要随意抽取方法。即使在真正工作中用的很多
myPut(map1, s.charAt(i));
myPut(map2, p.charAt(i));
}
//定义窗口左右两端
int start = 0;
int end = p.length()-1;
while (true) {
//如果两个map相匹配,把子串开始索引添加到list中
if (compare(map1, map2)) list.add(start);
//窗口右移
end++;
//while循环的结束条件放在这里。这样写不太优雅。但我不想动脑筋了
if (end >= s.length()) break;
//将右移的新元素添加到map中
myPut(map1, s.charAt(end));
//将左边移过去的元素去除
Integer i1 = map1.get(s.charAt(start));
//元素计数大于1就-1,否则就直接移除
if (i1 > 1) map1.put(s.charAt(start++), i1-1);
else map1.remove(s.charAt(start++));
}
return list;
}
//将新元素添加到map
private static void myPut(HashMap<Character, Integer> map, char charAt) {
Integer put = map.put(charAt, 1);
//如果为空说明添加成功,否则存在该元素,在原基础上数量+1
if (put != null) map.put(charAt, put+1);
}
//判断两个map中的元素是否相同,且数量一致
private static boolean compare(HashMap<Character, Integer> map1, HashMap<Character, Integer> map2) {
//遍历map1中的key
Set<Character> keySet1 = map1.keySet();
for (Character key : keySet1) {
//如果这个key在map2中找不到值,说明不存在,直接返回false
if (map2.get(key) == null) return false;
//这里一定要注意,不能用==比较两个Integer对象的大小。因为他们不是int这种基础数据类型。他们是对象,这是判断对象是否是同一个。
// 即使值一样,还是显示不相同。因为这是两个对象。在Integer中有个IntegerCache[],里面存储了-128~127之间的值,只有里面的值可以用==比较大小。
if (map1.get(key).compareTo(map2.get(key)) != 0) return false;
}
return true;
}
}