Algorithms · Ranges

Reorder to Win.

You have an array and a big batch of range queries — “what’s the answer on [l, r]?” — that you can answer offline (all at once, in any order). Mo’s algorithm keeps a single window with two pointers and slides them from one query’s range to the next, using cheap add and remove steps at the ends. If you processed the queries in their given order, the pointers would thrash back and forth for O(q·n) total movement. The trick is the order: sort queries by the √n-sized block of their left endpoint (then by right endpoint), and the total movement collapses to O((n+q)·√n). Watch reordering slash the pointer movement.

O((n+q)·√n) total · offline queries only · sort by √n block of L, then by R
offline

All queries are known up front and may be answered in any order.

window

One current range [curL, curR] with an incrementally maintained answer.

add / remove

Extend or shrink the window by one element at an end — a cheap O(1) update.

Mo's order

Sort queries by (block of L = ⌊L/√n⌋, then R). This bounds total pointer movement.

mo.js — the same queries, a smarter order
Ready
Eight range queries over a 16-element array. Sorted in Mo’s order, we keep one window and slide its ends to each query in turn. Step to process the next query and count the add/remove moves.
0/8
queries done
0
total moves
99
naive order

How it works

The magic is entirely in the sort key. Group the queries by which √n-sized block their left endpoint falls in. Within a block, sort by the right endpoint. Now two things are bounded. The left pointer only ever moves within a block between consecutive queries, so it travels at most √n per query — O(q·√n) overall. The right pointer, because queries within a block are sorted by R, marches mostly forward within each block and resets at most once per block, for O(n·√n / √n) = O(n·√n)… more precisely O(n·√n) total across all blocks. Add them and the whole batch costs O((n+q)·√n) pointer moves, each a constant-time add/remove — dramatically less than answering each query from scratch.

1

Collect queries offline

Read all q range queries first. Mo’s algorithm needs them up front because it will answer them in a rearranged order.

2

Sort by (block of L, then R)

Choose block size ≈ √n. Sort queries by ⌊L/√n⌋, breaking ties by R. This ordering is what bounds the total pointer travel.

3

Slide the window

Maintain one window [curL, curR] and its answer. For each query, move the two ends to the query’s l and r with add/remove steps, updating the running answer in O(1) each.

O((n+q)·√n) total

The sorted order keeps the left pointer within a block (√n per query) and the right pointer mostly monotic within a block, so all the sliding sums to O((n+q)·√n) — then map answers back to original query order.

Total time
O((n+q)·√n)
Naive
O(q·n)
Requirement
offline
Block size
√n

The code

# Mo's algorithm: answer q offline range queries block = int(n ** 0.5) queries.sort(key=lambda q: (q.l // block, q.r)) # the crucial order curL, curR, answer = 0, -1, 0 for q in queries: while curR < q.r: curR += 1; add(a[curR]) # extend right while curL > q.l: curL -= 1; add(a[curL]) # extend left while curR > q.r: remove(a[curR]); curR -= 1 # shrink right while curL < q.l: remove(a[curL]); curL += 1 # shrink left ans[q.id] = answer # O(1) running answer

Quick check

1. What does Mo’s algorithm require of the queries?

2. What is the key ordering Mo’s algorithm sorts queries by?

3. What is Mo’s algorithm’s total time for q queries on an n-element array?

FAQ

What is Mo's algorithm?

An offline technique for answering many range queries on a static array. It keeps one window with two pointers and a running answer, and processes queries sorted by (√n block of L, then R) so pointer movement between queries is cheap — O((n+q)·√n) total, far better than answering each independently.

Why does the √n block ordering make it fast?

Grouping by the √n block of L keeps the left pointer within a block (≤√n per query → O(q·√n)). Sorting within a block by R keeps the right pointer mostly forward, resetting once per block (→ O(n·√n)). The sum is O((n+q)·√n).

What problems is Mo's algorithm used for?

Range queries whose answer updates incrementally as elements enter/leave the window: distinct count in a range, most-frequent element, sum of squared frequencies, and many frequency/mode questions. It needs O(1)-ish add/remove updates and a static array.

What are the limitations of Mo's algorithm?

It’s offline-only, basic version needs a static array (a harder O(n^(5/3)) variant allows updates), and requires cheap add/remove answer maintenance. When you need online queries or frequent updates, a segment tree or similar is usually better.

SOLVE IT YOURSELF

Solve it: answer the queries out of order

Some range queries have no clean segment-tree merge — "how many distinct values in this range?" is the classic. Mo's trick: you are allowed to answer offline, so reorder the queries into an order where consecutive ones barely differ, then slide two pointers. Python or TypeScript.

YOUR TASK

Implement mos(data, queries) returning the count of distinct values in each inclusive range, in the original query order. Track MOVES: how many single-step pointer moves it took.

HINTS — 8 IDEAS
  1. Keep a window [cur_l, cur_r] and a frequency table. Widening by one element is O(1): bump its count, and if it just became 1 the distinct count rises.
  2. Shrinking is the mirror image: drop the count, and if it just hit 0 the distinct count falls. Every step is O(1) — only the number of steps needs controlling.
  3. Answering in the given order can cost O(n) per query. Since the queries are all known up front, you may answer them in any order and reorder the results afterwards.
  4. Sort by block of the left endpoint (block size ≈ √n), then by the right endpoint. Inside a block the right pointer only moves forward.
  5. That gives O((n+q)√n) total pointer movement: the right pointer sweeps at most n per block over √n blocks, and the left pointer moves at most √n per query.
  6. Alternate the right-endpoint direction per block if you want the constant factor down — same complexity, less zig-zag.
  7. Store each query's original index before sorting, or the answers come back scrambled. This is the single most common way to get Mo's wrong.
  8. It only works offline. If queries arrive one at a time and must be answered immediately, none of this is available to you.
CPython · WebAssembly

Keep going

Finished this one? 0 / 99 Algorithms done

Explore the topic

See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.

More Algorithms