題目描述
給定一個字符串 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” 的字母異位詞。
思路分析
滑動窗口算法。
- 左右指針,初始化都爲0,把[left,right]當成一個窗口;
- 不斷增大right,直到窗口內的子串符合要求;
- 不斷增大left,縮小窗口直到窗口內子串不符合要求,在縮小left時記錄結果;
- 直到right到達字符串串尾。
對於如何判斷是否符合要求,建立兩個哈希表,一個needs記錄pattern子串中字符出現次數,一個windows記錄當前窗口子串中字符出現次數。用一個match記錄符合規則的字符數。
當match等於needs的長度時,表明滿足條件,開始縮小窗口。每次縮小時,都要再次判斷條件,更新match。
注:Integer範圍是-128-127,在範圍內用緩存值,超過這個範圍會new一個對象,所以得用equals()比較地址
代碼實現
public static List<Integer> findAnagrams(String s, String p) {
List<Integer> list = new ArrayList<>();
if (s.length() == 0 || p.length() == 0) {
return list;
}
int left = 0, right = 0, match = 0;
Map<Character, Integer> windows = new HashMap<>();
Map<Character, Integer> needs = new HashMap<>();
//子串可能出現多次
for (char c : p.toCharArray()) {
Integer times = needs.getOrDefault(c, 0);
needs.put(c, times + 1);
}
char[] chars = s.toCharArray();
while (right < chars.length) {
char c1 = chars[right];
//模式串有這個字符
if (needs.containsKey(c1)) {
Integer times = windows.getOrDefault(c1, 0);
windows.put(c1, times + 1);
//一個字符匹配完成
if (needs.get(c1).equals(windows.get(c1))) {
match++;
}
}
right++;
//窗口已經符合要求
while (match == needs.size()) {
if (right - left == p.length()) {
//更新list,存left下標
list.add(left);
}
//存完就向右移動left
//先檢查left位置上的字符是不是要的字符
char c2 = chars[left];
if (needs.containsKey(c2)) {
Integer tmp = windows.get(c2);
tmp--;
//如果c2字符出現的次數少於模式串中出現的次數
if (tmp < needs.get(c2)) {
match--;
}
//tmp爲0就刪掉字符
if (tmp == 0) {
windows.remove(c2);
} else {
windows.put(c2, tmp);
}
}
left++;
}
}
return list;
}