Given two strings, we can determine if one contains a permutation of the other using a fixed-length sliding window approach. This technique efficiently checks for character matches by maintaining a window of characters and comparing frequency counts.
Problem 1: Detecting Permutation Substring
For the problem of determining if string s2 contanis any permutation of string s1, we can implement a solution using hash maps to track character frequencies:
function checkPermutationExists(s1, s2) {
// Create frequency map for s1
const targetFreq = new Map();
for (const char of s1) {
targetFreq.set(char, (targetFreq.get(char) || 0) + 1);
}
let windowStart = 0;
const windowFreq = new Map();
for (let windowEnd = 0; windowEnd < s2.length; windowEnd++) {
// Add current character to window
const currentChar = s2[windowEnd];
windowFreq.set(currentChar, (windowFreq.get(currentChar) || 0) + 1);
// Check if window size matches s1 length
if (windowEnd - windowStart + 1 === s1.length) {
// Compare frequency maps
if (compareMaps(windowFreq, targetFreq)) {
return true;
}
// Remove leftmost character from window
const leftChar = s2[windowStart];
windowFreq.set(leftChar, windowFreq.get(leftChar) - 1);
if (windowFreq.get(leftChar) === 0) {
windowFreq.delete(leftChar);
}
windowStart++;
}
}
return false;
}
function compareMaps(map1, map2) {
if (map1.size !== map2.size) {
return false;
}
for (const [key, value] of map1) {
if (map2.get(key) !== value) {
return false;
}
}
return true;
}
Problem 2: Finding All Anagram Indices
When we need to find all starting indices of anagrams of string p in string s, we can extend the previous approach:
function findAllAnagramIndices(s, p) {
// Create frequency map for pattern string
const patternFreq = new Map();
for (const char of p) {
patternFreq.set(char, (patternFreq.get(char) || 0) + 1);
}
let windowStart = 0;
const windowFreq = new Map();
const result = [];
for (let windowEnd = 0; windowEnd < s.length; windowEnd++) {
// Expand window to include current character
const currentChar = s[windowEnd];
windowFreq.set(currentChar, (windowFreq.get(currentChar) || 0) + 1);
// When window reaches pattern length
if (windowEnd - windowStart + 1 === p.length) {
// Check if current window is an anagram
if (compareMaps(windowFreq, patternFreq)) {
result.push(windowStart);
}
// Shrink window from left
const leftChar = s[windowStart];
windowFreq.set(leftChar, windowFreq.get(leftChar) - 1);
if (windowFreq.get(leftChar) === 0) {
windowFreq.delete(leftChar);
}
windowStart++;
}
}
return result;
}
// Map comparison helper function (same as in previous solution)
function compareMaps(map1, map2) {
if (map1.size !== map2.size) {
return false;
}
for (const [key, value] of map1) {
if (map2.get(key) !== value) {
return false;
}
}
return true;
}
Alternative Approach: Frequency Array
For strings containing only lowercase letters, we can optimize by using frequency arrays instead of hash maps:
function findAnagramsWithArray(s, p) {
// Initialize frequency array for pattern
const patternFreq = new Array(26).fill(0);
for (const char of p) {
patternFreq[char.charCodeAt(0) - 'a'.charCodeAt(0)]++;
}
const windowFreq = new Array(26).fill(0);
const result = [];
let windowStart = 0;
for (let windowEnd = 0; windowEnd < s.length; windowEnd++) {
// Add current character to window
const currentChar = s[windowEnd];
windowFreq[currentChar.charCodeAt(0) - 'a'.charCodeAt(0)]++;
// Check when window reaches pattern length
if (windowEnd - windowStart + 1 === p.length) {
// Compare frequency arrays
if (arraysEqual(windowFreq, patternFreq)) {
result.push(windowStart);
}
// Remove leftmost character from window
const leftChar = s[windowStart];
windowFreq[leftChar.charCodeAt(0) - 'a'.charCodeAt(0)]--;
windowStart++;
}
}
return result;
}
function arraysEqual(arr1, arr2) {
for (let i = 0; i < arr1.length; i++) {
if (arr1[i] !== arr2[i]) {
return false;
}
}
return true;
}