CMD Guide
HomeDSAAdvanced Patterns

Introduction to MO’s Algorithm Pattern

Mo's Algorithm answers a batch of offline range queries on a static array by sorting the queries so that consecutive queries have nearby endpoints, then sliding a two-pointer window [curL, curR] from one query's range to the next, adding or removing one element at a time instead of recomputing each answer from scratch. Grouping left endpoints into blocks of size about sqrt(N) bounds the total pointer movement across all Q queries to roughly O((N+Q)·sqrt(N)).

Recognize the pattern

Brute force to optimal

Brute force: for each query scan from L to R and recompute the answer independently. Cost O(Q·N) — with Q = N = 105 that is 1010 operations, far too slow.

Optimal (Mo's): pick block size B ≈ sqrt(N). Sort queries by (L / B), tie-broken by R (alternate ascending/descending R per block to avoid worst-case re-scans). Maintain a window [curL, curR] and an incrementally-updated answer; move curL and curR one step at a time to reach each query's [L, R] in sorted order.

Complexity, derived from first principles

Split total pointer movement into contributions from L and R separately.

Worked example

Array A = [1, 3, 5, 7, 9, 11, 13, 15] (N = 8). For this trace we pick block size B = 3 purely because it gives clean, easy-to-follow index arithmetic — B is a tunable parameter, not a fixed constant. Note that the shipped Java implementation below instead auto-computes block = (int) Math.sqrt(n), which for n = 8 gives block = 2, not 3; both are valid choices of B, they just differ between this illustration and that specific default. Queries (0-indexed, inclusive): Q1=[2,6], Q2=[0,2], Q3=[3,7].

Sort by block of L (using B=3): Q2 (L=0, block 0), Q1 (L=2, block 0), Q3 (L=3, block 1). Process in that order, sliding [curL,curR] and a running sum:

StepActioncurL,curRsum
init-0,-10
Q2=[0,2]add A[0],A[1],A[2]0,21+3+5=9
Q1=[2,6]remove A[0],A[1]; add A[3..6]2,65+7+9+11+13=45
Q3=[3,7]remove A[2]; add A[7]3,745-5+15=55

Total single-element moves here: 3 for init/Q2 (3 adds) + 6 for Q1 (2 removes + 4 adds) + 2 for Q3 (1 remove + 1 add) = 3 + 6 + 2 = 11 moves for 3 queries — far fewer than re-summing every range from scratch once N and Q grow large.

Java implementation

import java.util.*;

class MoQuery {
    int l, r, idx;
    MoQuery(int l, int r, int idx) { this.l = l; this.r = r; this.idx = idx; }
}

public class MoAlgorithm {

    public static long[] answerQueries(int[] a, int[][] queries) {
        int n = a.length, q = queries.length;
        int block = Math.max(1, (int) Math.sqrt(n));
        MoQuery[] qs = new MoQuery[q];
        for (int i = 0; i < q; i++) qs[i] = new MoQuery(queries[i][0], queries[i][1], i);

        Arrays.sort(qs, (x, y) -> {
            int bx = x.l / block, by = y.l / block;
            if (bx != by) return bx - by;
            return (bx % 2 == 0) ? x.r - y.r : y.r - x.r; // alternate direction
        });

        long[] ans = new long[q];
        long curSum = 0; // local accumulator: reset every call, no cross-call state leaks
        int curL = 0, curR = -1;
        for (MoQuery mq : qs) {
            while (curR < mq.r) curSum += a[++curR];
            while (curL > mq.l) curSum += a[--curL];
            while (curR > mq.r) curSum -= a[curR--];
            while (curL < mq.l) curSum -= a[curL++];
            ans[mq.idx] = curSum;
        }
        return ans;
    }
}

Note: curSum is a local variable declared inside answerQueries, so each call starts fresh at 0 — calling this method repeatedly (e.g. across multiple test cases in one process) never leaks state from a prior call. There is no unused freq field here: this example implements range-sum only, so only the state that sum actually needs (curSum) is declared.

Pitfalls

When to use / when not, and alternatives

Takeaways

Recall: Why does sorting queries by block of L, then alternating the sort direction of R per block, bound the total movement of the R pointer to O(N·N/B) instead of O(Q·N)?


Synthesized from competitive-programming references on Mo's Algorithm and Square Root Decomposition; complexity derivation is original to this page.

🤖 Don't fully get this? Learn it with Claude

Stuck on Introduction to MO’s Algorithm Pattern? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Introduction to MO’s Algorithm Pattern** (DSA) and want to truly understand it. Explain Introduction to MO’s Algorithm Pattern from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Introduction to MO’s Algorithm Pattern** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Introduction to MO’s Algorithm Pattern** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Introduction to MO’s Algorithm Pattern** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes