DSA

Sliding Window

22 Questions

Use a fixed-size sliding window: compute the sum of the first k elements, then slide by adding the next element and removing the first element of the previous window.

function maxSumSubarray(arr, k) {
    let sum = 0, maxSum = 0;
    for (let i = 0; i < k; i++) sum += arr[i];
    maxSum = sum;
    for (let i = k; i < arr.length; i++) {
        sum += arr[i] - arr[i - k];
        maxSum = Math.max(maxSum, sum);
    }
    return maxSum;
}
// maxSumSubarray([2, 1, 5, 1, 3, 2], 3) => 9

Time: O(n)  |  Space: O(1)

Use a variable-size sliding window: expand the right boundary to increase the sum, and shrink the left boundary when the sum meets the target.

function minSubarrayLen(target, nums) {
    let left = 0, sum = 0, minLen = Infinity;
    for (let right = 0; right < nums.length; right++) {
        sum += nums[right];
        while (sum >= target) {
            minLen = Math.min(minLen, right - left + 1);
            sum -= nums[left++];
        }
    }
    return minLen === Infinity ? 0 : minLen;
}
// minSubarrayLen(7, [2,3,1,2,4,3]) => 2 (subarray [4,3])

Time: O(n)  |  Space: O(1)

Use a sliding window with a Set to track characters. Expand right, and if a duplicate is found, shrink from the left until no duplicates remain.

function lengthOfLongestSubstring(s) {
    const set = new Set();
    let left = 0, maxLen = 0;
    for (let right = 0; right < s.length; right++) {
        while (set.has(s[right])) {
            set.delete(s[left++]);
        }
        set.add(s[right]);
        maxLen = Math.max(maxLen, right - left + 1);
    }
    return maxLen;
}
// lengthOfLongestSubstring("abcabcbb") => 3 ("abc")

Time: O(n)  |  Space: O(min(n, alphabet))

Sliding window: expand right, count zeros. When zeros exceed k, shrink from the left until zeros ≤ k again.

function longestOnes(nums, k) {
    let left = 0, zeros = 0, maxLen = 0;
    for (let right = 0; right < nums.length; right++) {
        if (nums[right] === 0) zeros++;
        while (zeros > k) {
            if (nums[left] === 0) zeros--;
            left++;
        }
        maxLen = Math.max(maxLen, right - left + 1);
    }
    return maxLen;
}
// longestOnes([1,1,0,0,1,1,1,0,1], 2) => 6

Time: O(n)  |  Space: O(1)

Find the longest subarray with at most 2 distinct elements (fruit types). Use a sliding window with a frequency map.

function totalFruit(fruits) {
    const map = new Map();
    let left = 0, maxLen = 0;
    for (let right = 0; right < fruits.length; right++) {
        map.set(fruits[right], (map.get(fruits[right]) || 0) + 1);
        while (map.size > 2) {
            const f = fruits[left];
            map.set(f, map.get(f) - 1);
            if (map.get(f) === 0) map.delete(f);
            left++;
        }
        maxLen = Math.max(maxLen, right - left + 1);
    }
    return maxLen;
}
// totalFruit([1,2,3,2,2]) => 4

Time: O(n)  |  Space: O(1) (at most 3 keys in map)

Use a fixed window of size s1.length on s2. Track character frequencies and compare with s1 frequencies.

function checkInclusion(s1, s2) {
    if (s1.length > s2.length) return false;
    const count = new Array(26).fill(0);
    for (let i = 0; i < s1.length; i++) {
        count[s1.charCodeAt(i) - 97]++;
        count[s2.charCodeAt(i) - 97]--;
    }
    if (count.every(c => c === 0)) return true;
    for (let i = s1.length; i < s2.length; i++) {
        count[s2.charCodeAt(i) - 97]--;
        count[s2.charCodeAt(i - s1.length) - 97]++;
        if (count.every(c => c === 0)) return true;
    }
    return false;
}
// checkInclusion("ab", "eidbaooo") => true

Time: O(n)  |  Space: O(1)

Use a variable sliding window with a frequency map. Expand right to include characters, shrink left to find the minimum valid window.

function minWindow(s, t) {
    const need = new Map();
    for (const c of t) need.set(c, (need.get(c) || 0) + 1);
    let left = 0, have = 0, required = need.size;
    let minLen = Infinity, result = "";
    const window = new Map();
    for (let right = 0; right < s.length; right++) {
        const c = s[right];
        window.set(c, (window.get(c) || 0) + 1);
        if (need.has(c) && window.get(c) === need.get(c)) have++;
        while (have === required) {
            if (right - left + 1 < minLen) {
                minLen = right - left + 1;
                result = s.slice(left, right + 1);
            }
            const d = s[left++];
            window.set(d, window.get(d) - 1);
            if (need.has(d) && window.get(d) < need.get(d)) have--;
        }
    }
    return result;
}

Time: O(m + n)  |  Space: O(m + n)

Find the longest substring where you can replace at most k characters to make all characters the same. Track the count of the most frequent character in the window.

function characterReplacement(s, k) {
    const count = new Array(26).fill(0);
    let left = 0, maxFreq = 0, maxLen = 0;
    for (let right = 0; right < s.length; right++) {
        count[s.charCodeAt(right) - 65]++;
        maxFreq = Math.max(maxFreq, count[s.charCodeAt(right) - 65]);
        while ((right - left + 1) - maxFreq > k) {
            count[s.charCodeAt(left) - 65]--;
            left++;
        }
        maxLen = Math.max(maxLen, right - left + 1);
    }
    return maxLen;
}
// characterReplacement("AABABBA", 1) => 4

Time: O(n)  |  Space: O(1)

Use the trick: exactly(k) = atMost(k) - atMost(k-1). Implement atMost(k) with a sliding window.

function subarraysWithKDistinct(nums, k) {
    return atMost(nums, k) - atMost(nums, k - 1);
}
function atMost(nums, k) {
    const map = new Map();
    let left = 0, count = 0;
    for (let right = 0; right < nums.length; right++) {
        map.set(nums[right], (map.get(nums[right]) || 0) + 1);
        while (map.size > k) {
            const v = nums[left];
            map.set(v, map.get(v) - 1);
            if (map.get(v) === 0) map.delete(v);
            left++;
        }
        count += right - left + 1;
    }
    return count;
}
// subarraysWithKDistinct([1,2,1,2,3], 2) => 7

Time: O(n)  |  Space: O(k)

Find the maximum in each window of size k. Use a deque (monotonic decreasing) to track the index of potential maximums efficiently.

function maxSlidingWindow(nums, k) {
    const deque = [], result = [];
    for (let i = 0; i < nums.length; i++) {
        // Remove indices outside the window
        if (deque.length && deque[0] < i - k + 1) deque.shift();
        // Remove smaller elements from back
        while (deque.length && nums[deque[deque.length-1]] < nums[i])
            deque.pop();
        deque.push(i);
        if (i >= k - 1) result.push(nums[deque[0]]);
    }
    return result;
}
// maxSlidingWindow([1,3,-1,-3,5,3,6,7], 3)
// => [3,3,5,5,6,7]

Time: O(n)  |  Space: O(k)

Expand the window. Track the most frequent character count in the window. If (window size - max freq) > k, shrink from left.

function characterReplacement(s, k) {
    const freq = {};
    let left = 0, maxFreq = 0, best = 0;
    for (let right = 0; right < s.length; right++) {
        freq[s[right]] = (freq[s[right]] || 0) + 1;
        maxFreq = Math.max(maxFreq, freq[s[right]]);
        // If replacements needed exceed k, shrink window
        if (right - left + 1 - maxFreq > k) {
            freq[s[left]]--;
            left++;
        }
        best = Math.max(best, right - left + 1);
    }
    return best;
}
// Input: s="AABABBA", k=1  => Output: 4  ("AABA" or "ABBA")
// Input: s="ABAB", k=2     => Output: 4  (replace both B's or A's)

Time: O(n)  |  Space: O(26) = O(1)

Use a sliding window of size k with a frequency map. Track distinct characters. Count windows where distinct ≤ j.

function countSubstringsAtMostK(s, k, j) {
    const freq = new Map();
    let left = 0, count = 0;
    for (let right = 0; right < s.length; right++) {
        freq.set(s[right], (freq.get(s[right]) || 0) + 1);
        while (freq.size > j) {
            const lc = s[left];
            freq.set(lc, freq.get(lc) - 1);
            if (freq.get(lc) === 0) freq.delete(lc);
            left++;
        }
        // All subarrays ending at right with distinct <= j
        count += right - left + 1;
    }
    return count;
}
// subarrays with exactly k distinct: atMostK(k) - atMostK(k-1)
function subarraysWithExactlyK(s, k) {
    return countSubstringsAtMostK(s, s.length, k)
         - countSubstringsAtMostK(s, s.length, k - 1);
}
// Input: s="araaci", k=2  => Output: 4 windows with exactly 2 distinct chars

Time: O(n)  |  Space: O(k)

Convert 0s to -1. Now the problem becomes: find the longest subarray with sum 0. Use a prefix sum + hash map approach — variable-size sliding window.

function findMaxLength(nums) {
    const map = new Map([[0, -1]]); // prefixSum -> first index
    let sum = 0, maxLen = 0;
    for (let i = 0; i < nums.length; i++) {
        sum += nums[i] === 1 ? 1 : -1; // convert 0 to -1
        if (map.has(sum)) {
            maxLen = Math.max(maxLen, i - map.get(sum));
        } else {
            map.set(sum, i); // only record first occurrence
        }
    }
    return maxLen;
}
// Input: [0,1]           => Output: 2
// Input: [0,1,0]         => Output: 2
// Input: [0,0,1,0,0,0,1,1] => Output: 6

Time: O(n)  |  Space: O(n)

Use a variable sliding window. Expand right, multiply. When product >= k, shrink left by dividing. Count windows: each right pointer gives (right - left + 1) valid subarrays.

function numSubarrayProductLessThanK(nums, k) {
    if (k <= 1) return 0;
    let product = 1, left = 0, count = 0;
    for (let right = 0; right < nums.length; right++) {
        product *= nums[right];
        while (product >= k) product /= nums[left++];
        count += right - left + 1;
    }
    return count;
}
// Input: nums=[10,5,2,6], k=100   => Output: 8
//   Subarrays: [10],[5],[2],[6],[10,5],[5,2],[2,6],[5,2,6]
// Input: nums=[1,2,3], k=0        => Output: 0

Time: O(n)  |  Space: O(1)

Use a deque to efficiently find the minimum subarray of length ≥ some size with sum ≥ target using prefix sums.

function shortestSubarray(nums, k) {
    const n = nums.length;
    const prefix = new Array(n + 1).fill(0);
    for (let i = 0; i < n; i++) prefix[i + 1] = prefix[i] + nums[i];
    let result = Infinity;
    const deque = []; // stores indices of prefix sum in increasing order
    for (let i = 0; i <= n; i++) {
        // Delete front while subarray sum >= k
        while (deque.length && prefix[i] - prefix[deque[0]] >= k) {
            result = Math.min(result, i - deque.shift());
        }
        // Maintain increasing deque
        while (deque.length && prefix[deque[deque.length-1]] >= prefix[i]) {
            deque.pop();
        }
        deque.push(i);
    }
    return result === Infinity ? -1 : result;
}
// Input: nums=[2,-1,2], k=3   => Output: 3  (whole array)
// Input: nums=[1,2], k=4      => Output: -1

Time: O(n)  |  Space: O(n)

Use a fixed-size sliding window with a frequency map. Maintain a counter of duplicates. A window is valid when duplicates = 0.

function countKLengthNoDup(arr, k) {
    const freq = new Map();
    let dupCount = 0, result = 0;
    for (let i = 0; i < arr.length; i++) {
        // Add right element
        freq.set(arr[i], (freq.get(arr[i]) || 0) + 1);
        if (freq.get(arr[i]) === 2) dupCount++; // new duplicate
        // Shrink from left when window exceeds k
        if (i >= k) {
            if (freq.get(arr[i - k]) === 2) dupCount--;
            freq.set(arr[i - k], freq.get(arr[i - k]) - 1);
        }
        if (i >= k - 1 && dupCount === 0) result++;
    }
    return result;
}
// Input: arr=[1,2,3,1,2,3], k=3  => Output: 2  ([1,2,3] at index 0 and 3)
// Input: arr=[2,2,2,2], k=2      => Output: 0  (all windows have dups)

Time: O(n)  |  Space: O(k)

Use a fixed sliding window. Count vowels in the first k characters, then slide: add the new character's vowel contribution, subtract the removed character's.

function maxVowels(s, k) {
    const vowels = new Set(['a','e','i','o','u']);
    let count = 0;
    for (let i = 0; i < k; i++) if (vowels.has(s[i])) count++;
    let maxCount = count;
    for (let i = k; i < s.length; i++) {
        if (vowels.has(s[i]))   count++;
        if (vowels.has(s[i-k])) count--;
        maxCount = Math.max(maxCount, count);
    }
    return maxCount;
}
// Input: s="abciiidef", k=3   => Output: 3  (window "iii")
// Input: s="aeiou", k=2       => Output: 2
// Input: s="leetcode", k=3    => Output: 2  ("lee" or "eet")

Time: O(n)  |  Space: O(1)

Take k cards total from either end. The optimal strategy leaves a window of (n-k) cards in the middle untouched. Minimize the middle window sum = maximize remaining sum.

function maxScore(cardPoints, k) {
    const n = cardPoints.length;
    const totalSum = cardPoints.reduce((a, b) => a + b, 0);
    // Find minimum sum of window of size (n-k)
    let windowSum = 0, minWindowSum = Infinity;
    for (let i = 0; i < n - k; i++) windowSum += cardPoints[i];
    minWindowSum = windowSum;
    for (let i = n - k; i < n; i++) {
        windowSum += cardPoints[i] - cardPoints[i - (n - k)];
        minWindowSum = Math.min(minWindowSum, windowSum);
    }
    return totalSum - minWindowSum;
}
// Input: cardPoints=[1,2,3,4,5,6,1], k=3   => Output: 12  (6+5+1 from ends)
// Input: cardPoints=[2,2,2], k=2            => Output: 4

Time: O(n)  |  Space: O(1)

Variable sliding window: expand right to grow sum, shrink left to minimize length while maintaining sum >= target.

function minSubArrayLen(target, nums) {
    let left = 0, sum = 0, minLen = Infinity;
    for (let right = 0; right < nums.length; right++) {
        sum += nums[right];
        while (sum >= target) {
            minLen = Math.min(minLen, right - left + 1);
            sum -= nums[left++]; // shrink from left
        }
    }
    return minLen === Infinity ? 0 : minLen;
}
// Input: target=7, nums=[2,3,1,2,4,3]   => Output: 2  ([4,3])
// Input: target=4, nums=[1,4,4]          => Output: 1  ([4])
// Input: target=11, nums=[1,1,1,1,1,1]  => Output: 0  (impossible)

Time: O(n)  |  Space: O(1)

Variable sliding window: expand right adding elements, shrink from left when sum exceeds k. Track the maximum window length where sum ≤ k.

function longestSubarrayAtMostK(nums, k) {
    let left = 0, sum = 0, maxLen = 0;
    for (let right = 0; right < nums.length; right++) {
        sum += nums[right];
        while (sum > k && left <= right) {
            sum -= nums[left++];
        }
        maxLen = Math.max(maxLen, right - left + 1);
    }
    return maxLen;
}
// Input: nums=[3,1,2,1,1], k=5   => Output: 4  ([1,2,1,1])
// Input: nums=[4,1,1,2], k=4     => Output: 3  ([1,1,2])
// Input: nums=[5,5], k=3         => Output: 0  (no subarray works)

Time: O(n)  |  Space: O(1)

Count total 1s (= window size). Find the minimum 0s in any window of that size — each 0 is a swap needed to bring a 1 in.

function minSwaps(nums) {
    const ones = nums.reduce((a, b) => a + b, 0);
    if (ones === 0) return 0;
    // Count 0s in first window of size 'ones'
    let zeros = 0;
    for (let i = 0; i < ones; i++) if (nums[i] === 0) zeros++;
    let minZeros = zeros;
    // Slide window on circular array (duplicate for circular)
    const n = nums.length;
    for (let i = ones; i < n + ones; i++) {
        if (nums[i % n] === 0) zeros++;
        if (nums[(i - ones) % n] === 0) zeros--;
        minZeros = Math.min(minZeros, zeros);
    }
    return minZeros;
}
// Input: [0,1,0,1,1,0,0]  => Output: 1  (swap one 0 into the window of 3 ones)
// Input: [1,0,1,0,1]      => Output: 1

Time: O(n)  |  Space: O(1)

Use a fixed-size sliding window of length p. Compare sorted (or frequency map) of window with sorted pattern. Record start index when they match.

function findPermutationIndices(s, p) {
    const result = [];
    if (p.length > s.length) return result;
    const pSorted = p.split('').sort().join('');
    for (let i = 0; i <= s.length - p.length; i++) {
        if (s.slice(i, i + p.length).split('').sort().join('') === pSorted) {
            result.push(i);
        }
    }
    return result;
}
// Optimized O(n) using frequency array (same as findAnagrams):
// Input: s="eidbaooo", p="ab"  => Output: [3]  ("ba" at index 3)
// Input: s="cbaebabacd", p="abc" => Output: [0,6]

Time: O(n × k log k) naive, O(n) optimized  |  Space: O(k)