Given two strings s
and p
, return an array of all the start indices of p
's anagrams in s
. You may return the answer in any order.
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".
Constraints:
1 <= s.length, p.length <= 3 * 104
s
and p
consist of lowercase English letters.This problem asks to find all starting indices of substrings within a string s
that are anagrams of another string p
. The most efficient approach is using a sliding window technique with frequency counters.
Approach:
Handle Trivial Cases: If the length of s
is less than the length of p
, there can be no anagrams, so return an empty list.
Frequency Counters: We use two frequency counters (arrays or hash maps):
cnt1
: Stores the frequency of each character in p
.cnt2
: Stores the frequency of characters in the current sliding window of s
.Sliding Window: We slide a window of size len(p)
across s
.
cnt2
is populated with the frequencies of characters in the first len(p) - 1
characters of s
.s
to cnt2
.cnt1
and cnt2
are equal. If they are, it means we've found an anagram, so add the starting index of the window to the result list.cnt2
to maintain the window size.Return Result: The function returns a list of starting indices where anagrams are found.
Time Complexity Analysis:
s
once. Each iteration involves a constant time comparison of frequency counters (assuming constant time hash map lookups).s
.Space Complexity Analysis:
cnt1
and cnt2
, which have a size at most equal to the number of unique characters in the alphabet (26 for lowercase English letters in this case).Code Examples (Python, Java, C++, Go, TypeScript, Rust, C#):
The code examples in various languages demonstrate the sliding window approach. All versions follow the same logic; only syntax differs.
The Python solution utilizing Counter
from the collections
module makes for especially concise and readable code:
from collections import Counter
class Solution:
def findAnagrams(self, s: str, p: str) -> List[int]:
ns, np = len(s), len(p)
if ns < np: return []
p_count = Counter(p)
s_count = Counter(s[:np-1])
output = []
for i in range(np -1, ns):
s_count[s[i]] += 1
if s_count == p_count:
output.append(i - np + 1)
s_count[s[i - np + 1]] -= 1
if s_count[s[i - np + 1]] == 0:
del s_count[s[i-np+1]]
return output
The Java, C++, Go, TypeScript, Rust and C# solutions use arrays of size 26 to represent the frequency counters, achieving the same functionality. They all exhibit O(n) time and O(1) space complexity. Note that the use of Arrays.equals
in Java necessitates a full array comparison each time, which is still O(1) in this context.