iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

Two Pointers

Two-pointer / sliding-window techniques solve subarray, substring, and pair problems in O(n) instead of O(n²). Two indices move through the data, expanding or contracting based on a condition.

Sum, longest substring, partition

EXAMPLE
// 1) Two sum on a SORTED array — O(n)
function twoSum(arr, target) {
    let i = 0, j = arr.length - 1;
    while (i < j) {
        const s = arr[i] + arr[j];
        if (s === target) return [i, j];
        if (s < target) i++;
        else            j--;
    }
    return null;
}

// 2) Remove duplicates from a sorted array in-place — O(n)
function removeDuplicates(arr) {
    if (arr.length === 0) return 0;
    let w = 0;
    for (let r = 1; r < arr.length; r++) {
        if (arr[r] !== arr[w]) {
            w++;
            arr[w] = arr[r];
        }
    }
    return w + 1;     // new length
}

// 3) Reverse an array in-place
function reverse(arr) {
    let i = 0, j = arr.length - 1;
    while (i < j) {
        [arr[i], arr[j]] = [arr[j], arr[i]];
        i++; j--;
    }
}

// 4) Container with most water (LeetCode 11)
function maxArea(h) {
    let i = 0, j = h.length - 1, best = 0;
    while (i < j) {
        const a = (j - i) * Math.min(h[i], h[j]);
        if (a > best) best = a;
        if (h[i] < h[j]) i++;
        else             j--;
    }
    return best;
}

// === Sliding window ===

// 5) Maximum sum subarray of size K — fixed window
function maxSumK(arr, k) {
    let sum = 0;
    for (let i = 0; i < k; i++) sum += arr[i];
    let best = sum;
    for (let i = k; i < arr.length; i++) {
        sum += arr[i] - arr[i - k];
        if (sum > best) best = sum;
    }
    return best;
}

// 6) Minimum-size subarray with sum >= target — variable window
function minSubLen(target, arr) {
    let l = 0, sum = 0, best = Infinity;
    for (let r = 0; r < arr.length; r++) {
        sum += arr[r];
        while (sum >= target) {
            best = Math.min(best, r - l + 1);
            sum -= arr[l++];
        }
    }
    return best === Infinity ? 0 : best;
}

// 7) Longest substring without repeating characters
function lengthOfLongestSubstring(s) {
    const seen = new Map();
    let l = 0, best = 0;
    for (let r = 0; r < s.length; r++) {
        const c = s[r];
        if (seen.has(c) && seen.get(c) >= l) l = seen.get(c) + 1;
        seen.set(c, r);
        best = Math.max(best, r - l + 1);
    }
    return best;
}

// 8) Permutation in string — does s2 contain any permutation of s1?
function checkInclusion(s1, s2) {
    if (s1.length > s2.length) return false;
    const need = new Array(26).fill(0);
    const have = new Array(26).fill(0);
    const A = 'a'.charCodeAt(0);
    for (const c of s1) need[c.charCodeAt(0) - A]++;
    let matches = 0;
    for (let i = 0; i < 26; i++) if (need[i] === 0) matches++;
    let l = 0;
    for (let r = 0; r < s2.length; r++) {
        const ri = s2.charCodeAt(r) - A;
        have[ri]++;
        if (have[ri] === need[ri]) matches++;
        else if (have[ri] === need[ri] + 1) matches--;
        if (r - l + 1 > s1.length) {
            const li = s2.charCodeAt(l) - A;
            if (have[li] === need[li]) matches--;
            else if (have[li] === need[li] + 1) matches++;
            have[li]--;
            l++;
        }
        if (matches === 26) return true;
    }
    return false;
}

// 9) Partition / move (Dutch national flag style)
function moveZeroesEnd(arr) {
    let w = 0;
    for (let r = 0; r < arr.length; r++) {
        if (arr[r] !== 0) {
            [arr[w], arr[r]] = [arr[r], arr[w]];
            w++;
        }
    }
}

// 10) Three sum — sort + fix-one + two-pointer for the rest
function threeSum(nums) {
    nums.sort((a, b) => a - b);
    const out = [];
    for (let i = 0; i < nums.length - 2; i++) {
        if (i > 0 && nums[i] === nums[i - 1]) continue;
        let l = i + 1, r = nums.length - 1;
        while (l < r) {
            const s = nums[i] + nums[l] + nums[r];
            if (s === 0) {
                out.push([nums[i], nums[l], nums[r]]);
                while (l < r && nums[l] === nums[l + 1]) l++;
                while (l < r && nums[r] === nums[r - 1]) r--;
                l++; r--;
            } else if (s < 0) l++;
            else              r--;
        }
    }
    return out;
}

// === When to reach for the technique ===
//   • Array / string with 'subarray with property X' or 'pair / triple with sum X'
//   • Sorted input (or sort-then-solve makes it tractable)
//   • 'Longest / shortest substring satisfying condition' → variable-size sliding window
//   • In-place partition / removal — read + write pointers
//   • Cycle detection in linked list — slow + fast pointer (Floyd)

// === Common pitfalls ===
//   • Forgetting to advance the inner pointer when the condition is met
//   • Off-by-one when shrinking the window (do you remove arr[l] before or after l++?)
//   • Mixed inclusive / exclusive bounds — decide once, stick with it
//   • Two-pointer on UNSORTED arrays for 'two sum' — that's a hash-map problem, not two-pointer

// === Decision flow ===
//   1. Is the array sorted (or can I sort it)?            → two-pointer maybe works
//   2. Am I looking for a subarray / substring property?  → sliding window
//   3. Pair / triplet sum problem?                         → sort + two-pointer
//   4. Otherwise → think hash map, prefix sum, or DP

Why it matters

Two-pointer turns nested loops into linear scans. The trick is identifying the “move l when X, move r when Y” rule — once you see it, half of array problems collapse to a 10-line solution.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
// Sorted array: find pair summing to target.
function pair(nums, t) {
    let l = 0, r = nums.length - 1;
    while (l < r) {
        const s = nums[l] + nums[r];
        if (s === t) return [l, r];
        if (s < t)   l++; else r--;
    }
    return [];
}
Try it Yourself »

Exercise

Direction to move when sum is too small (sorted).

if (s < target) l ; else r--;

Discussion

Loading…