Find All Starting Indices of Anagrams in a String
Solve this Problems and p, return every starting index in s where a contiguous substring is an anagram (a permutation, rearranged) of p.
This is the "find all" version of checking for a single permutation: instead of stopping at the first match, every window of s must be checked. A window is a match precisely when its 26-letter frequency count equals p's. Rebuilding that count for every window works, but the sliding windowSliding WindowMaintaining a running result over a contiguous range that grows or shrinks one element at a time, instead of recomputing the result for every range from scratch. technique keeps the count updated in O(1) per slide — bump the letter entering, drop the letter leaving — instead of recounting all of p.length letters every time.
Test Case 1:
Test Case 2:
Test Case 3:
Constraints
- ◆
1 ≤ s.length, p.length ≤ 3 × 10⁴ - ◆
s and p consist of lowercase English letters only
Try the Dry Run
Don't just read the solution — watch it execute, one step at a time.
| 1 | class Solution { |
| 2 | public int[] findAnagramStartIndices(String s, String p) { |
| 3 | int n = p.length(), m = s.length(); |
| 4 | int[] temp = new int[Math.max(m, 1)]; |
| 5 | int count = 0; |
| 6 | if (n > m) return new int[0]; |
| 7 | int[] need = new int[26], window = new int[26]; |
| 8 | for (char c : p.toCharArray()) need[c - 'a']++; |
| 9 | for (int i = 0; i < n; i++) window[s.charAt(i) - 'a']++; |
| 10 | if (Arrays.equals(need, window)) temp[count++] = 0; |
| 11 | for (int i = n; i < m; i++) { |
| 12 | window[s.charAt(i) - 'a']++; |
| 13 | window[s.charAt(i - n) - 'a']--; |
| 14 | if (Arrays.equals(need, window)) temp[count++] = i - n + 1; |
| 15 | } |
| 16 | return Arrays.copyOf(temp, count); |
| 17 | } |
| 18 | } |
| 19 |
"aa"Build p's frequency count and the very first window of s, "aa".
Approach & Solutions
Brute Force
BruteBuild a 26-letter frequency count for p. Then for every window of length p.length in s, build that window's own frequency count from scratch and compare it against p's, recording the start index on a match. Correct, but rebuilding a fresh count for every single window throws away almost all of the work done for the window right before it.
O(n · 26)O(n)1class Solution {
2 public int[] findAnagramStartIndices(String s, String p) {
3 int n = p.length(), m = s.length();
4 int[] temp = new int[Math.max(m, 1)];
5 int count = 0;
6 if (n > m) return new int[0];
7 int[] need = new int[26];
8 for (char c : p.toCharArray()) need[c - 'a']++;
9 for (int i = 0; i <= m - n; i++) {
10 int[] window = new int[26];
11 for (int j = i; j < i + n; j++) window[s.charAt(j) - 'a']++;
12 if (Arrays.equals(need, window)) temp[count++] = i;
13 }
14 return Arrays.copyOf(temp, count);
15 }
16}Optimal — Sliding Window
OptimalBuild p's 26-letter frequency count once, and the very first window's frequency count once. Compare them. Then slide the window one character at a time: bump the count of the character entering on the right, drop the count of the character leaving on the left, and compare again — recording the start index whenever it matches — no need to rebuild the whole window's count from scratch on every slide.
O(n)O(n)1class Solution {
2 public int[] findAnagramStartIndices(String s, String p) {
3 int n = p.length(), m = s.length();
4 int[] temp = new int[Math.max(m, 1)];
5 int count = 0;
6 if (n > m) return new int[0];
7 int[] need = new int[26], window = new int[26];
8 for (char c : p.toCharArray()) need[c - 'a']++;
9 for (int i = 0; i < n; i++) window[s.charAt(i) - 'a']++;
10 if (Arrays.equals(need, window)) temp[count++] = 0;
11 for (int i = n; i < m; i++) {
12 window[s.charAt(i) - 'a']++;
13 window[s.charAt(i - n) - 'a']--;
14 if (Arrays.equals(need, window)) temp[count++] = i - n + 1;
15 }
16 return Arrays.copyOf(temp, count);
17 }
18}