Home/Learn/Coordinate Compression

Pattern Guide

Coordinate Compression

"Map large values to small ranks. Enables Fenwick/SegTree on value-based problems."

Coordinate compression maps a set of large values (up to 10^9) to small ranks (1..n) without changing relative order. This allows using data structures like Fenwick Trees and Segment Trees on problems where values span a huge range. Prerequisite for many hard problems involving counting inversions, LIS with Fenwick, and 2D range queries.

14 min readdp problems →

Problems you can solve with this pattern

4 problems · click any to start solving

All dp →
1Count of Smaller Numbers After SelfHardSolve
2Count of Range SumHardSolve
3Longest Increasing Subsequence (O(n log n) with compression)MediumSolve
4Number of Longest Increasing SubsequenceMediumSolve
Coordinate compression template
// Given values that can be up to 10^9, compress to ranks 1..n
function compress(values) {
    // Step 1: collect all unique values
    const unique = [...new Set(values)].sort((a,b) => a-b);
    // Step 2: build rank map
    const rank = new Map(unique.map((v,i) => [v, i+1])); // 1-indexed
    // Step 3: replace values with ranks
    return { ranks: values.map(v => rank.get(v)), n: unique.length, rank, unique };
}

// Lower bound: rank of x = first rank with unique[rank-1] >= x
// Used for: "count elements less than x" = query(lowerBound(x) - 1)

// Example: values = [100, 50, 200, 50, 100]
// unique = [50, 100, 200] → ranks: [2, 1, 3, 1, 2]
// Fenwick tree of size 3 instead of 200!

// Combined with Fenwick tree for counting/inversions:
function countInversions(nums) {
    const unique = [...new Set(nums)].sort((a,b)=>a-b);
    const rank = new Map(unique.map((v,i)=>[v,i+1]));
    const n = unique.length;
    const bit = new Array(n+1).fill(0);
    const update = i => { for(;i<=n;i+=i&(-i)) bit[i]++; };
    const query = i => { let s=0; for(;i>0;i-=i&(-i)) s+=bit[i]; return s; };
    let inversions = 0;
    for(const x of nums) {
        const r = rank.get(x);
        inversions += query(n) - query(r); // count elements already inserted that are > x
        update(r);
    }
    return inversions;
}

Coordinate compression solves the "values are too large for array indexing" problem. If you need a Fenwick tree on values up to 10^9, you can't allocate 10^9 slots. Instead: collect all values that appear, sort them, and replace each value with its rank (1..n). The relative order is preserved — that's all you need for count-based queries.

The Technique

Coordinate compression template
// Given values that can be up to 10^9, compress to ranks 1..n
function compress(values) {
    // Step 1: collect all unique values
    const unique = [...new Set(values)].sort((a,b) => a-b);
    // Step 2: build rank map
    const rank = new Map(unique.map((v,i) => [v, i+1])); // 1-indexed
    // Step 3: replace values with ranks
    return { ranks: values.map(v => rank.get(v)), n: unique.length, rank, unique };
}

// Lower bound: rank of x = first rank with unique[rank-1] >= x
// Used for: "count elements less than x" = query(lowerBound(x) - 1)

// Example: values = [100, 50, 200, 50, 100]
// unique = [50, 100, 200] → ranks: [2, 1, 3, 1, 2]
// Fenwick tree of size 3 instead of 200!

// Combined with Fenwick tree for counting/inversions:
function countInversions(nums) {
    const unique = [...new Set(nums)].sort((a,b)=>a-b);
    const rank = new Map(unique.map((v,i)=>[v,i+1]));
    const n = unique.length;
    const bit = new Array(n+1).fill(0);
    const update = i => { for(;i<=n;i+=i&(-i)) bit[i]++; };
    const query = i => { let s=0; for(;i>0;i-=i&(-i)) s+=bit[i]; return s; };
    let inversions = 0;
    for(const x of nums) {
        const r = rank.get(x);
        inversions += query(n) - query(r); // count elements already inserted that are > x
        update(r);
    }
    return inversions;
}
When to use coordinate compression:
- Values up to 10^9 but only n ≤ 10^5 distinct values appear
- Need a Fenwick/Segment tree indexed by VALUE (not position)
- Counting/querying by value range: how many elements ≤ x

Template: collect all values, sort, deduplicate, map each → rank. Then use ranks as array indices.

Offline vs online: Coordinate compression requires knowing all values upfront (offline). For online queries (values arrive one by one), use a balanced BST or dynamic segment tree instead.