Fixed-Length Sliding Window for String Permutation Problems

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;
}

Tags: sliding-window string-permutation hash-map frequency-array algorithm

Posted on Sun, 14 Jun 2026 17:53:30 +0000 by condoug