Check whether any permutation of s1 appears as a contiguous substring of s2. The key is a fixed-size sliding window of width s1.length and a single integer matches counter that tracks how many of the 26 letter-counts are currently in sync — no recount needed on each slide.
Given strings s1 and s2, return true if any permutation of s1 is a contiguous substring of s2. s1="ab", s2="eidbaooo" → true because "ba" (a permutation of "ab") appears at index 3. s1="ab", s2="eidboaoo" → false.
s2 is a permutation of s1 iff their letter-frequency arrays are identical. Instead of recomputing the full array on every slide, maintain a single matches counter (0–26). Each slide updates at most two characters (the one entering and the one leaving), so matches changes by at most ±2. When matches === 26, all counts agree — done.s1.length > s2.length → impossible, return false immediately.s1 into a 26-slot array.have[] for s2[0..k-1]. For each slot, if have[c] === need[c] increment matches; if it just crossed from equal to unequal, decrement.matches === 26, return true.r: add s2[r] and remove s2[r-k], updating matches for each. Check again.have[c] goes from equal to over, you must decrement matches(the slot just became unequal). Many people only increment on the "just became equal" path and forget the symmetric decrement on the "just left equal" path — causing phantom matches.Space is O(1) because the alphabet is fixed at 26 — two number[26] arrays regardless of input size. If you needed arbitrary Unicode, use a Map (O(k) space).
k = 2. Build need[] from s1, initialize have[] and matches = 0.1function checkInclusion(s1: string, s2: string): boolean {2▶ const k = s1.length;3 if (k > s2.length) return false;45 // Frequency arrays for s1 (need) and the current window of s2 (have)6▶ const need = new Array<number>(26).fill(0);7▶ const have = new Array<number>(26).fill(0);8▶ const a = 'a'.charCodeAt(0);910▶ for (const c of s1) need[c.charCodeAt(0) - a]++;1112 // matches = number of letters whose count in 'have' equals 'need'13▶ let matches = 0;1415 // Seed the first window16 for (let i = 0; i < k; i++) {17 const idx = s2.charCodeAt(i) - a;18 have[idx]++;19 if (have[idx] === need[idx]) matches++;20 else if (have[idx] - 1 === need[idx]) matches--;21 }22 if (matches === 26) return true;2324 // Slide one step at a time25 for (let r = k; r < s2.length; r++) {26 // Bring the right character into the window27 const rIdx = s2.charCodeAt(r) - a;28 have[rIdx]++;29 if (have[rIdx] === need[rIdx]) matches++;30 else if (have[rIdx] - 1 === need[rIdx]) matches--;3132 // Evict the leftmost character33 const lIdx = s2.charCodeAt(r - k) - a;34 have[lIdx]--;35 if (have[lIdx] === need[lIdx]) matches++;36 else if (have[lIdx] + 1 === need[lIdx]) matches--;3738 if (matches === 26) return true;39 }40 return false;41}
function checkInclusion(s1: string, s2: string): boolean {
const k = s1.length;
if (k > s2.length) return false;
// Frequency arrays for s1 (need) and the current window of s2 (have)
const need = new Array<number>(26).fill(0);
const have = new Array<number>(26).fill(0);
const a = 'a'.charCodeAt(0);
for (const c of s1) need[c.charCodeAt(0) - a]++;
// matches = number of letters whose count in 'have' equals 'need'
let matches = 0;
// Seed the first window
for (let i = 0; i < k; i++) {
const idx = s2.charCodeAt(i) - a;
have[idx]++;
if (have[idx] === need[idx]) matches++;
else if (have[idx] - 1 === need[idx]) matches--;
}
if (matches === 26) return true;
// Slide one step at a time
for (let r = k; r < s2.length; r++) {
// Bring the right character into the window
const rIdx = s2.charCodeAt(r) - a;
have[rIdx]++;
if (have[rIdx] === need[rIdx]) matches++;
else if (have[rIdx] - 1 === need[rIdx]) matches--;
// Evict the leftmost character
const lIdx = s2.charCodeAt(r - k) - a;
have[lIdx]--;
if (have[lIdx] === need[lIdx]) matches++;
else if (have[lIdx] + 1 === need[lIdx]) matches--;
if (matches === 26) return true;
}
return false;
}s1 is longer than s2 no window can hold it — bail immediately.need[] tallies s1's letter counts; have[] will track the sliding window. Both are fixed 26-slot arrays, so space stays O(1).s2[0..k-1], incrementing have[c] each time. After each increment, check both directions: if the slot just became equal to need, matches++; if it just left equal (was equal, now over), matches--. Start by checking right after the first window.s2[r] (right edge) and removes s2[r-k] (left edge). Both updates apply the same ±1 matches logic. The window is a permutation the instant matches === 26.false.l to a result array every time matches === 26.number[26] array with a Map<string, number>and track the number of "satisfied" keys rather than the count 26.s2 and compare to the sorted s1 — O(n · k log k). The count approach avoids re-sorting by maintaining state incrementally.function checkInclusion(s1: string, s2: string): boolean {
const k = s1.length;
if (k > s2.length) return false;
// Frequency arrays for s1 (need) and the current window of s2 (have)
const need = new Array<number>(26).fill(0);
const have = new Array<number>(26).fill(0);
const a = 'a'.charCodeAt(0);
for (const c of s1) need[c.charCodeAt(0) - a]++;
// matches = number of letters whose count in 'have' equals 'need'
let matches = 0;
// Seed the first window
for (let i = 0; i < k; i++) {
const idx = s2.charCodeAt(i) - a;
have[idx]++;
if (have[idx] === need[idx]) matches++;
else if (have[idx] - 1 === need[idx]) matches--;
}
if (matches === 26) return true;
// Slide one step at a time
for (let r = k; r < s2.length; r++) {
// Bring the right character into the window
const rIdx = s2.charCodeAt(r) - a;
have[rIdx]++;
if (have[rIdx] === need[rIdx]) matches++;
else if (have[rIdx] - 1 === need[rIdx]) matches--;
// Evict the leftmost character
const lIdx = s2.charCodeAt(r - k) - a;
have[lIdx]--;
if (have[lIdx] === need[lIdx]) matches++;
else if (have[lIdx] + 1 === need[lIdx]) matches--;
if (matches === 26) return true;
}
return false;
}A window is a permutation of s1 exactly when its sorted characters equal the sorted s1. Slide a fixed-width window of length k = s1.length across s2, sort each window, and compare.
function checkInclusion(s1: string, s2: string): boolean {
const k = s1.length;
if (k > s2.length) return false;
const target = s1.split('').sort().join('');
for (let i = 0; i + k <= s2.length; i++) {
const window = s2.slice(i, i + k).split('').sort().join('');
if (window === target) return true;
}
return false;
}n − k + 1windows costs O(k log k), so the whole scan is O(n · k log k) — re-sorting from scratch throws away all the work done on the previous window. Maintaining a 26-slot frequency count incrementally (one char enters, one leaves per slide) drops it to O(n).| "does s2 contain a permutation / anagram of s1?" | fixed-size sliding window + 26-count match counter |
| window size is fixed, known up front | seed first window, then slide — no need for two-pointer expand/shrink |
| comparing letter frequencies of two strings | number[26] arrays + matches counter avoids full recount |
| "find all anagram start indices" (LC 438) | same window; collect l instead of returning true |
const k = s1.length;
if (k > s2.length) return false;
const need = new Array<number>(26).fill(0);
const have = new Array<number>(26).fill(0);
const a = 'a'.charCodeAt(0);
for (const c of s1) need[c.charCodeAt(0) - a]++;
let matches = 0;
// seed first window, track matches
for (let i = 0; i < k; i++) { /* add s2[i] to have; update matches */ }
if (matches === 26) return true;
for (let r = k; r < s2.length; r++) {
// add s2[r], remove s2[r-k], update matches both times
if (matches === 26) return true;
}
return false;have[c] === need[c] for all 26 letters, i.e. matches === 26.checkInclusion(s1, s2)?s1="ab", s2="eidbaooo", what is the window size?c to have[], when should you decrement matches?s1="ab", s2="eidboaoo". Does the function return true?if (k > s2.length) return false necessary?