INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR
01
00/10
01 / COVER STEP 02 · SORTING
INVARIANT · STEP 02 · ONE DECK
NINE WAYS TO ORDER

Everyone can name three sorting algorithms. Far fewer can say which one a problem is quietly asking for — stable or not, in place or not, already-nearly-sorted or adversarial. That choice is the whole topic.

10Problems
9Units
7Lectures
12Visualisers
← → ↑ ↓  or  W A S D  navigate SPACE next   DOUBLE-CLICK to advance I index   G goto problem   P predict H hide solutions   T close video   F fullscreen
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
02 / START HERE READ THIS ONE FIRST

HOW THIS DECK WORKS

This is not a list of problems. It is 9 learning units, and every one runs the same four beats. Go in order and the problems stop feeling random.

01 INTROWhat the concept is, and what to watch for
02 VIDEOThe lecture, full-width in theatre mode
03 DRILLS2–4 questions checking the lecture landed
04 PROBLEMSThe sheet problems that concept unlocks

Moving around

← ↑Back a slide — or A / W → ↓Forward — or D / S / Space 2×clickDouble-click the right side to advance, left to go back. A single click never moves the deck. IThe index: every problem, clickable, with your progress GJump straight to a problem by its number FFullscreen

While you study

HHide solutions — blurs code and steps so you try first PPredict mode: call the next step before the animation plays it TClose the video — Esc works too ☐ ★Mark solved, or star to revisit. Both are saved automatically.

10 PROBLEMS · EVERY ONE LINKS TO A JUDGE TO SOLVE

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
03 / INDEX PRESS I FROM ANYWHERE

INDEX — ALL 10 PROBLEMS

Click any problem to jump straight to it. Solved ones turn green. Press I from anywhere to come back here. The number is the sheet's row, not your position — the deck teaches in the order that builds, so you will meet them slightly out of numerical sequence.

SORTING-I · 03
SORTING-II · 04
★ SORTING AS A TOOL · 03
SOLVED HAS A JUDGE LINK
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
04 / SIGNALS WHEN YOU SEE X, REACH FOR Y

WHICH SORT, AND WHY

Nobody is asked to “write a sorting algorithm” in an interview. You are handed constraints and expected to hear which one they permit — and twice in this deck the answer is that sorting is not the answer at all.

“SORT THE ARRAY”, n ≤ 10⁵

No stability or memory constraint stated.

Library sort — or quick sort if asked to implementO(n log n)
“KEEP EQUAL RECORDS IN ORDER”

Ties must not be reordered — usually a second sort key.

Merge sort · insertion sortO(n log n) · O(n) space
“NO EXTRA MEMORY” + A GUARANTEE

Both in-place and worst-case bounded.

Heap sort — the only one giving bothO(n log n) · O(1)
VALUES IN A SMALL FIXED RANGE

Ages, scores, 0/1/2, letters of an alphabet.

Counting sort — no comparisons at allO(n + k)
“COUNT PAIRS OUT OF ORDER”

Pair-counting at a size that forbids all-pairs.

Merge sort, counting during the mergeO(n log n)
INTERVALS · MEETINGS · OVERLAP

Order is not the answer, it is what makes the answer cheap.

Sort by start, then one sweepO(n log n)
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
05 / CONSTRAINTS BEFORE YOU WRITE A LINE

READ THE ANSWER OFF THE CONSTRAINTS

Before writing anything, read the input bound and it tells you the complexity you are allowed. Type a value for n and the row that survives lights up.

n ≤
BUDGET
WHAT THAT BUYS YOU
10–12
O(n!)
Permutations, brute-force TSP
20–25
O(2ⁿ) · O(2ⁿ·n)
Subsets, bitmask DP, meet-in-the-middle
100
O(n³)
Floyd–Warshall, matrix chain, interval DP
10³
O(n²)
2D DP, all-pairs loops — and the quadratic three
10⁵
O(n log n)
Sorting, heaps, binary search on answers, two pointers
10⁶–10⁸
O(n) · O(log n)
Single pass, prefix sums, sieve, counting and radix sort

THE GOLD-EDGED ROWS ARE WHERE THIS TOPIC LIVES · QUADRATIC SORTS DIE AT 10³ · COUNTING AND RADIX REACH 10⁷

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
06 / ROADMAP WHAT YOU ARE ABOUT TO DO

THE WHOLE RUN, 9 UNITS

Nine units. The first five are the sheet; the last four are lectures with no sheet row at all, kept because they are where the comparison lower bound gets broken — the best idea in the topic, and the sheet does not cover it.

UNIT 01

The Quadratic Three

▶ 43:444 DRILLS3 PROBLEMS
UNIT 02

Merge Sort

▶ 49:434 DRILLS2 PROBLEMS
UNIT 03

The Recursive Rewrites

NO LECTURE3 DRILLS2 PROBLEMS
UNIT 04

Quick Sort

▶ 35:173 DRILLS1 PROBLEM
UNIT 05

Sorting As A Tool

NO LECTURE3 DRILLS2 PROBLEMS
BEYOND THE SHEETUNIT 06

Heap Sort

▶ 46:033 DRILLSNO SHEET ROW
BEYOND THE SHEETUNIT 07

Counting Sort

▶ 31:403 DRILLSNO SHEET ROW
BEYOND THE SHEETUNIT 08

Radix Sort

▶ 34:133 DRILLSNO SHEET ROW
BEYOND THE SHEETUNIT 09

Shell Sort

▶ 34:073 DRILLSNO SHEET ROW
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
07 / WARMUP LOAD THE TOPIC BEFORE UNIT 01 · 1 OF 2

THREE THINGS WORTH HAVING IN MEMORY

DRILL 01 · RECALL

You need to sort 10⁵ integers within a one-second limit. Which of these is already too slow?

10⁵ squared is 10¹⁰, and a second buys you roughly 10⁸ operations — so the quadratic sorts are out by two orders of magnitude before you write a line. n log n here is about 1.7 × 10⁶, which is nothing. The constraint told you which family to reach for, and the next slide makes that reading mechanical.

DRILL 02 · RECALL

A sort is called stable when…

Stability is about ties. If two records compare equal, a stable sort leaves them in their original order. It sounds academic until you sort employees by department having already sorted them by name — with an unstable sort the names scramble inside each department. Merge and insertion are stable; quick, heap and shell are not, and that is often the deciding factor.

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
08 / WARMUP LOAD THE TOPIC BEFORE UNIT 01 · 2 OF 2

THREE THINGS WORTH HAVING IN MEMORY

DRILL 01 · TRACE

One pass turns [5, 1, 4, 2] into [1, 5, 2, 4]. Compare the two arrays: what kind of move produced this?

Two swaps of adjacent values. Read the pairs: 5 and 1 traded places, then 4 and 2 did. Nothing moved more than one slot, and no value jumped across the array — so whatever did this only ever compares neighbours. That is a real constraint, and it is why this family cannot be fast: to move a value k places you need k separate swaps. Unit 01 names the three algorithms that work this way; the point here is only that you can see the limitation in the data before you know any of their names.

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
09 / INTRO UNIT 01 · The Quadratic Three

UNIT 01 — The Quadratic Three

Three algorithms, one shape: a growing sorted region on one side and a shrinking unsorted region on the other. What separates them is where the sorted region is and what one pass costs to extend it — and that difference is the reason two of them survive in real libraries and one does not.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU SORT WITH NOTHING BUT COMPARISONS AND SWAPS?

PASSSORTED PREFIXSWAP vs SHIFTIN PLACEBEST CASE
WHAT TO WATCH FOR
  • 01SELECTION SCANS THE WHOLE TAIL BEFORE IT MOVES ANYTHING — SO IT CANNOT FINISH EARLY
  • 02BUBBLE'S didSwap FLAG IS DECLARED INSIDE THE OUTER LOOP, NEVER OUTSIDE IT
  • 03INSERTION SHIFTS RIGHT — ONE WRITE PER STEP, NOT THE THREE A SWAP COSTS
  • 04THE SUM (n−1) + (n−2) + … + 1 ON THE BOARD IS WHERE THE n²/2 COMES FROM
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
10 / VIDEO UNIT 01 · The Quadratic Three

Sorting Part 1 — Selection, Bubble, Insertion

STRIVER A2Z
The Quadratic Three
RUNTIME 43:44
AFTER THIS → 4 DRILLS · PROBLEM #01, #02, #03
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
11 / DRILL UNIT 01 · The Quadratic Three · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

Hand selection sort an array that is already perfectly sorted. What does it cost?

Still O(n²), and this is the property the lecture opens with. Selection sort's inner loop has no exit condition that depends on the data — it looks at every remaining element to be certain it has the minimum, sorted input or not. It performs zero swaps on sorted input and the full n(n−1)/2 = 28 comparisons anyway. Comparisons are fixed; only the swap count reacts to the input.

DRILL 02 · TRACE

Bubble sort runs ONE complete pass over [7, 2, 9, 4, 1, 8, 3, 6]. What sits at the last index afterwards?

9. Every comparison pushes the larger of the pair one slot right, so the maximum gets picked up wherever it sits and carried all the way to the end — that is the “bubbling” the name refers to. One pass places exactly one value, and it is always the maximum of the unsorted region. Step the visualiser in PREDICT mode and it asks you this once per pass.

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
12 / DRILL UNIT 01 · The Quadratic Three · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · BUG

This is the insertion sort shift loop, and one line is wrong. It compiles, it runs, and it silently returns a wrong array. Which line?

while (j >= 0 && a[j] > key) {
    a[j] = a[j + 1];        // shift
    j--;
}
a[j + 1] = key;

The assignment is backwards. Shifting means copying the larger value into the gap on its right, so it reads a[j + 1] = a[j]. Written the other way it copies the gap's stale contents leftward, overwriting real data with garbage — no crash, no warning, just a wrong array. This is the exact line the lecture slows down for: the gap moves left, the values move right.

DRILL 02 · TRANSFER

A log file arrives almost sorted — a handful of entries are out of place. Which of the three finishes fastest, and why?

Insertion. Its inner while stops as soon as it meets something smaller than the key, so on nearly-sorted input it barely runs and the whole sort collapses to O(n). Bubble's flag also gives O(n), but only on input that is already fully sorted — a single misplaced element at the front still costs it a full set of passes to carry across. This is exactly why real library sorts hand small or nearly-ordered ranges to insertion sort rather than continuing to recurse.

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
13 / MECHANISM UNIT 01 · SELECTION · CODE MIRRORED

SELECTION SORT — SCAN EVERYTHING, MOVE ONCE

The pass never stops early and never learns anything from the data: it looks at every remaining value before it moves a single one. Comparisons are fixed at 28 no matter what you feed it — but it makes at most n−1 swaps, which is why it survives where writes are expensive.

THE SAME EIGHT VALUES, EVERY ALGORITHM
CODE MIRROR
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
14 / MECHANISM UNIT 01 · BUBBLE · CODE MIRRORED

BUBBLE SORT — NEIGHBOURS ONLY, AND THAT IS THE PROBLEM

Every swap moves a value exactly one slot, so a value at the wrong end has to be carried across the array one exchange at a time. Watch the swap counter climb. The one thing it does better than selection: a clean pass proves the array is sorted, so sorted input costs O(n).

THE SAME EIGHT VALUES, EVERY ALGORITHM
CODE MIRROR
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
15 / MECHANISM UNIT 01 · INSERTION · CODE MIRRORED

INSERTION SORT — SHIFT, DO NOT SWAP

The left side is always sorted. Each new value is lifted out and the larger ones shift right to open a gap — one write per shift, not three per swap. On nearly-sorted input the inner loop barely runs, which is why real libraries switch to insertion sort for small or almost-ordered ranges.

THE SAME EIGHT VALUES, EVERY ALGORITHM
CODE MIRROR
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
16 / PROBLEM #01 · QUADRATIC · EASY

Selection Sort

EASY quadratic ▶ SOLVE ON GEEKSFORGEEKS
SIGNAL — WHAT GIVES IT AWAY

The task names the algorithm outright, so the signal is not which algorithm but which invariant you have to protect: after pass i, the first i slots are final and are never read again.

INTUITION

Split the array into a sorted prefix and an unsorted tail. One pass finds the minimum of the tail and swaps it to the tail's front, which extends the prefix by one. Repeat n−1 times — the last element has nowhere left to go, so it is already correct by elimination.

STEPS
  1. Loop i from 0 to n−2 — the final element needs no pass of its own
  2. Assume mn = i, the front of the unsorted tail
  3. Scan j from i+1 to n−1 and keep the index of anything smaller
  4. Swap a[i] with a[mn] once, after the scan is complete
  5. Slot i is now final; never touch it again
↕ SCROLL
// Selection sort: find the minimum of the tail, then move it ONCE.
// The scan records an index; nothing is written until the scan is over.
void selectionSort(vector<int>& a) {
    int n = a.size();

    for (int i = 0; i < n - 1; i++) {       // n-1 passes, not n
        int mn = i;                         // assume the front is smallest
        for (int j = i + 1; j < n; j++)     // scan the WHOLE tail, always
            if (a[j] < a[mn]) mn = j;       // record only, do not swap here
        swap(a[i], a[mn]);                  // ONE write per pass, at most
    }
}
TIMEO(n²)n(n−1)/2 comparisons, whatever the input
SPACEO(1)swaps in place, no auxiliary array
TRAP

Swapping inside the scan instead of after it is the mistake that still produces a sorted array — so the judge passes it and you never learn. It turns one swap per pass into up to n−1 of them, and selection sort's only advantage over bubble sort is that it writes at most n−1 times. Find the index first, swap once.

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
SOLUTION #01 · SELECTION SORT

Sorting Part 1 — Selection, Bubble, Insertion

The walkthrough for #01 Selection Sort. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
Sorting Part 1 — Selection, Bubble, Insertion
RUNTIME 43:44
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
17 / PROBLEM #02 · QUADRATIC · EASY

Bubble Sort

EASY quadratic ▶ SOLVE ON GEEKSFORGEEKS
SIGNAL — WHAT GIVES IT AWAY

Adjacent comparison only. The moment a problem restricts you to swapping neighbours, the number of swaps you need is fixed — it is the inversion count — and bubble sort is what performs exactly that many.

INTUITION

Walk the array comparing each pair of neighbours and swap them if they are out of order. The largest value meets a smaller neighbour at every step, so it gets carried to the end in a single pass. Shrink the range by one and repeat. If a whole pass swaps nothing, no pair is out of order anywhere — the array is sorted and you can stop.

STEPS
  1. Loop i from n−1 down to 1 — the current last index of the unsorted region
  2. Reset didSwap = false at the START of each pass
  3. Compare a[j] with a[j+1] for j from 0 to i−1 and swap if out of order
  4. If didSwap is still false at the end of a pass, break — nothing is out of order
  5. Otherwise shrink i and run the next pass
↕ SCROLL
// Bubble sort: swap neighbours only. The flag is the whole optimisation
// and it must be reset once per PASS -- outside the loop it never re-arms.
void bubbleSort(vector<int>& a) {
    int n = a.size();

    for (int i = n - 1; i > 0; i--) {
        bool didSwap = false;               // INSIDE: reset every pass
        for (int j = 0; j < i; j++)         // j < i: the tail is already final
            if (a[j] > a[j + 1]) {
                swap(a[j], a[j + 1]);       // NEIGHBOURS only
                didSwap = true;
            }
        if (!didSwap) break;                // a clean pass proves it is sorted
    }
}
TIMEO(n²) · O(n) bestquadratic in general; one clean pass on sorted input
SPACEO(1)adjacent swaps in place
TRAP

Declaring didSwap outside the outer loop is the version that looks optimised and is not. Set to true by the very first real swap, it never returns to false, so the early exit can never fire and the best case quietly reverts to O(n²). The answer stays correct, which is exactly why nobody notices. It belongs inside the outer loop, reset once per pass.

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
SOLUTION #02 · BUBBLE SORT

Sorting Part 1 — Selection, Bubble, Insertion

The walkthrough for #02 Bubble Sort. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
Sorting Part 1 — Selection, Bubble, Insertion
RUNTIME 43:44
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
18 / PROBLEM #03 · QUADRATIC · EASY

Insertion Sort

EASY quadratic ▶ SOLVE ON GEEKSFORGEEKS
SIGNAL — WHAT GIVES IT AWAY

Elements arriving one at a time, or an array that is already nearly in order. Both point at insertion sort, because it is the only one of the three whose cost drops when the input is already good — and the only one that can sort a stream it has not finished reading.

INTUITION

Treat the left of the array as sorted — one element is sorted by definition. Lift the next value out into a variable, leaving a genuine hole. Slide every larger value one slot right; the hole travels left with them. When you meet something smaller, the hole is exactly where the value belongs, so drop it in.

STEPS
  1. Loop i from 1 to n−1 — a[0] alone is already a sorted prefix
  2. key = a[i]; the slot at i is now free to be overwritten
  3. Walk j back from i−1 while a[j] > key
  4. Copy a[j] to a[j+1] — a SHIFT, one write, not a three-write swap
  5. Place key at a[j+1] once the walk stops
↕ SCROLL
// Insertion sort: SHIFT the larger values right, then drop the key in.
// One write per shift -- a swap would cost three, for the same movement.
void insertionSort(vector<int>& a) {
    int n = a.size();

    for (int i = 1; i < n; i++) {           // a[0] is a sorted prefix already
        int key = a[i];                     // lift it out, leaving a gap
        int j = i - 1;
        while (j >= 0 && a[j] > key) {      // '>' not '>=' -- keeps it STABLE
            a[j + 1] = a[j];                // shift right; the gap moves left
            j--;
        }
        a[j + 1] = key;                     // drop into the gap
    }
}
TIMEO(n²) · O(n) bestthe inner loop stops early on ordered input
SPACEO(1)one variable holds the key; shifts happen in place
TRAP

Writing the inner loop as a chain of swap calls instead of shifts sorts correctly and costs three times the writes — each swap is a read, two writes and a temporary, where a shift is one write. Worse, using a[j] >= key rather than > makes the sort unstable: equal elements get reordered, and every problem that sorts records by one field while relying on a previous ordering silently breaks.

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
SOLUTION #03 · INSERTION SORT

Sorting Part 1 — Selection, Bubble, Insertion

The walkthrough for #03 Insertion Sort. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
Sorting Part 1 — Selection, Bubble, Insertion
RUNTIME 43:44
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
19 / INTRO UNIT 02 · Merge Sort

UNIT 02 — Merge Sort

The first algorithm here that is not quadratic — and the reason is structural, not clever. Splitting costs nothing and compares nothing; all the work is in putting two already sorted halves back together, and that is cheap precisely because only the two fronts can ever compete.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU GET BELOW n² BY DOING NOTHING ON THE WAY DOWN?

DIVIDE AND CONQUERMERGESTABLEO(n) EXTRAlog n LEVELS
WHAT TO WATCH FOR
  • 01NOT ONE COMPARISON HAPPENS WHILE SPLITTING — ONLY WHILE MERGING
  • 02THE TEMP ARRAY IS INDEXED FROM 0, THE REAL ARRAY FROM low — THAT OFFSET IS THE BUG
  • 03log n LEVELS × O(n) PER LEVEL IS THE WHOLE COMPLEXITY ARGUMENT
  • 04THE MERGE TAKES FROM THE LEFT ON A TIE — THAT ONE `<=` IS WHAT MAKES IT STABLE
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
20 / VIDEO UNIT 02 · Merge Sort

Merge Sort — Algorithm, Pseudocode, Dry Run, Code

STRIVER A2Z
Merge Sort
RUNTIME 49:43
AFTER THIS → 4 DRILLS · PROBLEM #04, #08
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
21 / DRILL UNIT 02 · Merge Sort · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

In merge sort, where do the comparisons between elements actually happen?

Only in the merge. The split is pure arithmetic — mid = (lo + hi) / 2 — and never looks at a value. This is the point the lecture opens on and it is what makes the cost analysis so clean: each of the log n levels does O(n) merging work and nothing else, so the total is O(n log n) with no cases and no caveats.

DRILL 02 · TRACE

The final merge combines the sorted halves [2, 4, 7, 9] and [1, 3, 6, 8]. Which value is written THIRD?

3. Only the two fronts can compete: 2 vs 1 → write 1; then 2 vs 3 → write 2; then 4 vs 3 → write 3. That is the whole reason a merge is linear rather than quadratic — you never look past the front of either half, because both halves are already sorted. Run the visualiser in PREDICT mode and it asks you exactly this at every merge.

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
22 / DRILL UNIT 02 · Merge Sort · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · BUG

This is the copy-back after a merge. It compiles, runs, and scrambles the array on every subarray except the leftmost. Which line is wrong?

for (int i = 0; i < temp.size(); i++)
    a[i] = temp[i];
// temp holds the merged range a[low..high]

a[low + i] = temp[i]. The temp array is indexed from 0 but the range it belongs to starts at low. Forget the offset and every merge dumps its result at the front of the array. The leftmost subarray has low == 0, so it works there — which is exactly why this bug survives a quick test and then fails the judge. The lecture writes this offset out deliberately.

DRILL 02 · TRANSFER

Merge sort needs O(n) extra space and quick sort needs O(1). Why is merge sort still the one used for linked lists?

Random access. A merge only ever reads the front of each half and advances — perfectly natural for a linked list, and on a list you can relink instead of copying, so the O(n) space disappears too. Partitioning, by contrast, walks two pointers inward from both ends, and walking backwards through a singly linked list is exactly what it cannot do.

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
23 / MECHANISM UNIT 02 · MERGE · CODE MIRRORED

MERGE SORT — THE WORK HAPPENS ON THE WAY UP

Nothing is compared while splitting. All the ordering happens in the merges, and a merge is cheap precisely because both halves are already sorted — only the two fronts can ever compete. log n levels × O(n) per level is the whole complexity argument, visible as the depth counter.

THE SAME EIGHT VALUES, EVERY ALGORITHM
CODE MIRROR
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
24 / PROBLEM #04 · DIVIDE · MED

Merge Sort

MED divide ▶ SOLVE ON GEEKSFORGEEKS
SIGNAL — WHAT GIVES IT AWAY

The statement says sort and the constraints say n up to 10⁵ — that alone rules out the quadratic three. When it also needs to be stable, or the data is a linked list, or you need a guaranteed bound rather than an average one, merge sort is the specific answer.

INTUITION

Split the range in half until each piece holds one element, which is sorted by definition. Then merge pairs of sorted ranges back together. A merge is linear because both inputs are already ordered, so only their two front elements can compete — and there are log n levels of merging, each doing O(n) work.

STEPS
  1. If lo >= hi the range holds 0 or 1 elements — return, it is sorted
  2. mid = lo + (hi - lo) / 2; recurse on [lo, mid] and [mid+1, hi]
  3. Walk both halves with two indices, always taking the smaller front
  4. Take from the LEFT on a tie — that single <= is what keeps it stable
  5. Drain whichever half still has elements left
  6. Copy temp back with the low offset: a[low + i] = temp[i]
↕ SCROLL
// Merge sort: nothing is compared on the way DOWN. All the ordering
// happens in merge(), which is cheap because both halves are sorted.
void merge(vector<int>& a, int low, int mid, int high) {
    vector<int> temp;
    int i = low, j = mid + 1;

    while (i <= mid && j <= high)
        temp.push_back(a[i] <= a[j] ? a[i++] : a[j++]);   // <= keeps it STABLE
    while (i <= mid)  temp.push_back(a[i++]);
    while (j <= high) temp.push_back(a[j++]);

    for (int k = 0; k < (int)temp.size(); k++)
        a[low + k] = temp[k];      // low + k, NOT k -- the offset is the bug
}

void mergeSort(vector<int>& a, int low, int high) {
    if (low >= high) return;                    // 0 or 1 elements
    int mid = low + (high - low) / 2;           // not (low+high)/2 -- overflow

    mergeSort(a, low, mid);
    mergeSort(a, mid + 1, high);
    merge(a, low, mid, high);
}
TIMEO(n log n)log n levels, each merging O(n) elements
SPACEO(n)one temp buffer the width of the merged range
TRAP

Copying the temp array back as a[i] = temp[i] instead of a[low + i] is the mistake that passes your own test. The very first merge has low == 0, so a small example sorts perfectly and every later subarray gets dumped at the front of the array. Also compute mid as lo + (hi - lo) / 2: (lo + hi) overflows a 32-bit int once the indices get large, and that bug lived in the JDK for nine years.

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
SOLUTION #04 · MERGE SORT

Merge Sort — Algorithm, Pseudocode, Dry Run, Code

The walkthrough for #04 Merge Sort. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
Merge Sort — Algorithm, Pseudocode, Dry Run, Code
RUNTIME 49:43
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
25 / PROBLEM #08 · DIVIDE · HARD

Count Inversions

HARD divide ▶ SOLVE ON GEEKSFORGEEKS
SIGNAL — WHAT GIVES IT AWAY

“Count the pairs where i < j but a[i] > a[j].” Counting pairs is O(n²) by definition, so the constraint n ≤ 10⁵ is telling you the pairs must be counted in groups rather than one at a time — and “out of order” plus “grouped” means merge sort.

INTUITION

An inversion is a pair that a sort would have to swap past each other. Bubble sort makes exactly one adjacent swap per inversion, which is why it is O(n²) — it pays one operation per pair. Merge sort's merge can settle many at once: when you take a value from the right half, every element still left in the left half is greater than it and comes before it, so all of them form an inversion with it. Add mid - i + 1 in one go.

STEPS
  1. Run a normal merge sort — the sort itself is completely unchanged
  2. Inside the merge, when a[i] <= a[j], take from the left and count nothing
  3. When a[i] > a[j] you must take from the right
  4. Every remaining element of the left half is > a[j] and sits before it
  5. So add (mid - i + 1) to the counter, not 1
  6. Return the total, which needs to be a long long
↕ SCROLL
// Count Inversions = merge sort with a counter. The SORT is unchanged;
// only the merge learns to count, and it counts in groups.
long long merge(vector<int>& a, int low, int mid, int high) {
    vector<int> temp;
    int i = low, j = mid + 1;
    long long inv = 0;                  // 32-bit overflows: max is ~5e9

    while (i <= mid && j <= high) {
        if (a[i] <= a[j]) {
            temp.push_back(a[i++]);     // in order -- nothing to count
        } else {
            inv += (mid - i + 1);       // ALL of the left half beats a[j]
            temp.push_back(a[j++]);
        }
    }
    while (i <= mid)  temp.push_back(a[i++]);
    while (j <= high) temp.push_back(a[j++]);

    for (int k = 0; k < (int)temp.size(); k++)
        a[low + k] = temp[k];
    return inv;
}

long long countInv(vector<int>& a, int low, int high) {
    if (low >= high) return 0;
    int mid = low + (high - low) / 2;

    long long inv = countInv(a, low, mid);
    inv += countInv(a, mid + 1, high);
    inv += merge(a, low, mid, high);    // pairs SPANNING the two halves
    return inv;
}
TIMEO(n log n)the merge counts in groups, so no extra pass is needed
SPACEO(n)the same temp buffer merge sort already uses
TRAP

Adding 1 instead of mid - i + 1 is the wrong answer that looks careful — it counts one inversion per merge step rather than per pair, and on small examples the two numbers can even coincide. The other one: the answer can reach n(n−1)/2, which for n = 10⁵ is about 5 × 10⁹ and overflows a 32-bit int. Use long long.

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
SOLUTION #08 · COUNT INVERSIONS

Merge Sort — Algorithm, Pseudocode, Dry Run, Code

The walkthrough for #08 Count Inversions. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
Merge Sort — Algorithm, Pseudocode, Dry Run, Code
RUNTIME 49:43
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
26 / INTRO UNIT 03 · The Recursive Rewrites

UNIT 03 — The Recursive Rewrites

No new algorithm here and no new complexity — the same two sorts you already know, with the outer loop written as recursion. It is worth doing once because it is the cleanest example of a mechanical transformation you will use constantly in DP: a loop variable becomes a parameter, and the loop's exit becomes the base case.

THE QUESTION THIS LECTURE ANSWERS

WHAT ACTUALLY CHANGES WHEN A LOOP BECOMES RECURSION?

BASE CASERECURSION PARAMETERCALL STACKTAIL CALL
WHAT TO WATCH FOR
  • 01THE OUTER LOOP VARIABLE BECOMES THE FUNCTION'S PARAMETER — NOTHING ELSE MOVES
  • 02THE LOOP'S TERMINATION CONDITION BECOMES THE BASE CASE, INVERTED
  • 03THE INNER LOOP IS UNTOUCHED — RECURSION REPLACES ONE LOOP, NOT BOTH
  • 04COMPLEXITY IS IDENTICAL, BUT SPACE IS NOW O(n) OF CALL STACK, NOT O(1)
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
27 / DRILL UNIT 03 · The Recursive Rewrites · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

Converting bubble sort's outer loop to recursion. What becomes the base case?

Pass size 1. The loop ran for (i = n-1; i > 0; i--), so it stopped when i reached 1 — and that condition, inverted, is the base case: if (n == 1) return;. That is the whole mechanical rule. The early-exit on a clean pass is a second, separate return, and it is an optimisation rather than the base case — conflating the two is how the conversion goes wrong.

DRILL 02 · TRANSFER

Recursive bubble sort and iterative bubble sort — how do their complexities compare?

Time is identical; space is not. Exactly the same comparisons and swaps happen in exactly the same order — the recursion is only bookkeeping. But each pending call keeps a stack frame, so n nested calls cost O(n) stack where the loop cost nothing. This is a real trade-off to be able to state, and it is why the recursive form is a teaching device rather than an improvement.

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
28 / DRILL UNIT 03 · The Recursive Rewrites · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · BUG

Recursive bubble sort that overflows the stack on every input. Which line is at fault?

void bubble(vector<int>& a, int n) {
    if (n == 1) return;
    for (int j = 0; j < n - 1; j++)
        if (a[j] > a[j + 1]) swap(a[j], a[j + 1]);
    bubble(a, n);
}

bubble(a, n - 1). Recursion needs the argument to move toward the base case; calling with the same n re-runs an identical pass forever. It is the single most common recursion bug and the reason to state the rule as one sentence: every recursive call must make the problem strictly smaller. The pass itself is correct — only the argument is.

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
29 / MECHANISM UNIT 03 · REWRITES · CODE MIRRORED

WHERE THE WORK SITS IS THE WHOLE DIFFERENCE

Both algorithms split and recurse, so the split is not what separates them. Merge chooses its halves by position and does nothing on the way down — all the work is the combine, after both calls return, which is why it needs O(n) scratch. Quick partitions by value first, so the pivot reaches its final slot on the way down and there is nothing left to do coming back — which is why it sorts in place.

THE SAME EIGHT VALUES, EVERY ALGORITHM
CODE MIRROR
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
30 / PROBLEM #05 · RECURSIVE · EASY

Recursive Bubble Sort

EASY recursive ▶ SOLVE ON GEEKSFORGEEKS
SIGNAL — WHAT GIVES IT AWAY

Not a new problem — bubble sort with the outer loop expressed as recursion. The signal to read is the transformation itself: a loop whose variable only ever shrinks is a recursion parameter waiting to happen.

INTUITION

One pass of bubble sort drives the largest value of the first n elements to index n−1. That is exactly the work the outer loop did once per iteration. So do one pass, then ask the function to sort the first n−1 elements. The inner loop does not change at all.

STEPS
  1. Base case: n == 1 — a single element is already sorted, return
  2. Run one full pass of adjacent comparisons over a[0..n-2]
  3. Track didSwap; if nothing swapped the whole array is sorted, return early
  4. Otherwise recurse on n − 1, shrinking the problem
↕ SCROLL
// Bubble sort with the OUTER loop as recursion. The inner loop is
// untouched -- recursion replaces one loop, never both.
void bubbleSort(vector<int>& a, int n) {
    if (n == 1) return;                 // the loop's exit, inverted

    bool didSwap = false;
    for (int j = 0; j < n - 1; j++)     // one full pass, exactly as before
        if (a[j] > a[j + 1]) {
            swap(a[j], a[j + 1]);
            didSwap = true;
        }

    if (!didSwap) return;               // early exit survives the rewrite
    bubbleSort(a, n - 1);               // n - 1: the problem MUST shrink
}
TIMEO(n²) · O(n) bestidentical comparisons to the loop version
SPACEO(n)n nested calls, each holding a stack frame
TRAP

Recursing with the same n is an instant stack overflow, and it is easy to write because the pass itself looks complete. The subtler cost is the one people forget to mention in interviews: this version is O(n) space, not O(1). The recursion buys readability, not efficiency — say so before you are asked.

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
31 / PROBLEM #06 · RECURSIVE · EASY

Recursive Insertion Sort

EASY recursive ▶ SOLVE ON GEEKSFORGEEKS
SIGNAL — WHAT GIVES IT AWAY

Insertion sort with the outer loop as recursion. Same transformation as the previous problem, applied to the algorithm whose inner loop is a shift rather than a swap — so the thing that changes is again only the outer structure.

INTUITION

Insertion sort's invariant is that a[0..i-1] is sorted. Recursively sort the first i elements, then insert a[i] into that sorted prefix by shifting. The recursion runs forwards, not backwards: you must sort the smaller prefix before you can insert into it.

STEPS
  1. Base case: i == n — every index has been inserted, return
  2. Assume a[0..i-1] is already sorted
  3. Lift key = a[i] and shift every larger value in the prefix one slot right
  4. Drop key into the gap the shifting left behind
  5. Recurse on i + 1
↕ SCROLL
// Insertion sort with the OUTER loop as recursion. Note the direction:
// this counts UP toward n, because the prefix must be sorted first.
void insertionSort(vector<int>& a, int i, int n) {
    if (i == n) return;                 // every index inserted

    int key = a[i];                     // lift it out, leaving a gap
    int j = i - 1;
    while (j >= 0 && a[j] > key) {      // '>' not '>=' -- keeps it STABLE
        a[j + 1] = a[j];                // shift right; the gap moves left
        j--;
    }
    a[j + 1] = key;                     // drop into the gap

    insertionSort(a, i + 1, n);         // i + 1: forwards, not backwards
}
TIMEO(n²) · O(n) bestthe shift loop still stops early on ordered input
SPACEO(n)one stack frame per index
TRAP

This one recurses upwardi + 1 toward n — where recursive bubble sort recursed downward toward 1. Copying the previous problem's shape and decrementing gives you a function that inserts into a prefix it has not sorted yet, and the result is subtly out of order rather than obviously broken.

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
32 / INTRO UNIT 04 · Quick Sort

UNIT 04 — Quick Sort

Quick sort throws away merge sort's one expensive habit: there is no combine step at all. Partition alone does everything, by driving a single value to the position it will occupy in the final array — and once a pivot lands, it is never examined again. That is what buys O(1) extra space.

THE QUESTION THIS LECTURE ANSWERS

CAN YOU SORT BY DIVIDING, WITHOUT EVER MERGING BACK?

PIVOTPARTITIONIN PLACEWORST CASERANDOMISED PIVOT
WHAT TO WATCH FOR
  • 01AFTER PARTITION THE PIVOT IS IN ITS FINAL SLOT — NOT NEAR IT, EXACTLY IT
  • 02THE TWO RECURSIVE CALLS SKIP THE PIVOT: qs(lo, j-1) AND qs(j+1, hi)
  • 03NOTHING IS RETURNED OR COMBINED — THE ARRAY IS SORTED IN PLACE AS YOU GO
  • 04A SORTED INPUT WITH A FIRST-ELEMENT PIVOT IS THE O(n²) WORST CASE, NOT THE BEST
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
33 / VIDEO UNIT 04 · Quick Sort

Quick Sort For Beginners

STRIVER A2Z
Quick Sort
RUNTIME 35:17
AFTER THIS → 3 DRILLS · PROBLEM #07
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
34 / DRILL UNIT 04 · Quick Sort · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

Partition has just finished. What is guaranteed about the pivot?

Its exact final position. Everything to its left is ≤ it and everything to its right is > it, so no future operation can have any reason to move it — and the recursion is deliberately written to exclude it, qs(lo, j-1) and qs(j+1, hi). Getting this is what makes it obvious why there is no merge step: each call leaves its own slot permanently correct, so there is nothing left to combine.

DRILL 02 · TRACE

Partition [7, 2, 9, 4, 1, 8, 3, 6] with the leftmost element as pivot. What index does 7 end up at?

Index 5. You do not need to simulate the pointer dance to know this — just count how many values are smaller than the pivot: 2, 4, 1, 3 and 6, which is five. So five values must sit to its left and 7 lands at index 5, giving [3, 2, 6, 4, 1, 7, 8, 9]. The pivot's final index is always the count of values below it — a fast sanity check on any partition you write.

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
35 / DRILL UNIT 04 · Quick Sort · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · TRAP

You hand quick sort an already-sorted array of 10⁵ elements, using the first element as pivot. What happens?

O(n²), and the recursion goes n deep. The pivot is the smallest element every single time, so partition puts nothing on its left and everything on its right — the array shrinks by one per call instead of halving. This is the case the lecture warns about, and it is genuinely nasty because sorted or nearly-sorted input is common in the real world. The fix is one line: pick a random pivot, or median-of-three, and swap it to the front.

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
36 / MECHANISM UNIT 04 · QUICK · CODE MIRRORED

QUICK SORT — ONE VALUE LANDS FOR GOOD, EVERY TIME

No merge step exists. Partition alone does the work: when the two scans meet, the pivot swaps into place and is never examined again — everything left of it is smaller, everything right is larger. That is what buys O(1) extra space where merge sort needs O(n).

THE SAME EIGHT VALUES, EVERY ALGORITHM
CODE MIRROR
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
37 / PROBLEM #07 · DIVIDE · EASY

Quick Sort

EASY divide ▶ SOLVE ON GEEKSFORGEEKS
SIGNAL — WHAT GIVES IT AWAY

Sorting wanted in place, with no extra array allowed, and an average rather than guaranteed bound is acceptable. Also the giveaway for the whole family: any problem that only needs one element in its final position — kth largest, median — wants partition, not a full sort.

INTUITION

Choose a pivot. Rearrange the range so everything ≤ pivot sits left of it and everything greater sits right. The pivot is now exactly where it belongs and can be ignored forever. Recurse on the two sides. Because each call fixes its own pivot in place, there is nothing to merge afterwards.

STEPS
  1. If lo >= hi the range is trivially sorted — return
  2. Take pivot = a[lo]; set i = lo, j = hi
  3. Advance i while a[i] <= pivot; retreat j while a[j] > pivot
  4. If i < j they straddle a misplaced pair — swap and continue
  5. When they cross, swap the pivot into position j
  6. Recurse on [lo, j-1] and [j+1, hi], excluding the pivot
↕ SCROLL
// Quick sort: no combine step at all. Partition drives ONE value to its
// final slot, and that slot is never touched again.
int partition(vector<int>& a, int low, int high) {
    int pivot = a[low], i = low, j = high;

    while (i < j) {
        while (i < high && a[i] <= pivot) i++;
        while (j > low  && a[j] >  pivot) j--;
        if (i < j) swap(a[i], a[j]);
    }
    swap(a[low], a[j]);                 // the pivot lands, FOR GOOD
    return j;
}

void quickSort(vector<int>& a, int low, int high) {
    if (low >= high) return;
    int p = partition(a, low, high);

    quickSort(a, low, p - 1);           // p is excluded from both sides --
    quickSort(a, p + 1, high);          // it is already final
}
TIMEO(n log n) avg · O(n²) worstbalanced partitions halve the range; degenerate ones do not
SPACEO(log n)recursion depth only — no auxiliary array
TRAP

Using the first element as pivot makes already-sorted input the worst case: every partition splits into 0 and n−1, giving O(n²) and a recursion n deep that will overflow the stack at 10⁵. Sorted input is common in practice, so this is not a theoretical concern. Randomise the pivot — or take median-of-three — and swap it to lo before partitioning, and the whole failure mode disappears for one line.

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
SOLUTION #07 · QUICK SORT

Quick Sort For Beginners

The walkthrough for #07 Quick Sort. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
Quick Sort For Beginners
RUNTIME 35:17
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
38 / INTRO UNIT 05 · Sorting As A Tool

UNIT 05 — Sorting As A Tool

Every unit so far produced order. These two consume it. In both, sorting is not the answer — it is the move that makes an O(n²) problem collapse into a single pass, and recognising that is worth more than any one algorithm in this deck.

THE QUESTION THIS LECTURE ANSWERS

WHEN IS SORTING THE SETUP RATHER THAN THE ANSWER?

DUTCH FLAG3-WAY PARTITIONSWEEPSORT-THEN-SCANINVARIANT
WHAT TO WATCH FOR
  • 01SORT COLORS IS ONE PASS — SORTING IT PROPERLY WOULD BE THE SLOW ANSWER
  • 02IN THE DUTCH FLAG, mid DOES NOT ADVANCE AFTER A SWAP WITH high
  • 03MERGE INTERVALS SORTS BY START SO THAT ONLY THE LAST KEPT INTERVAL CAN OVERLAP
  • 04BOTH TURN AN ALL-PAIRS QUESTION INTO A NEIGHBOUR QUESTION — THAT IS THE PATTERN
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
39 / DRILL UNIT 05 · Sorting As A Tool · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

In the Dutch national flag partition, a[mid] is 2 and you swap it with a[high]. Does mid advance?

No, and this is the entire bug surface of the problem. When you swap with low you know what comes back — everything before mid is already classified, so it must be a 1 and mid can safely advance. But high is the unexamined region: the value it hands you has never been looked at, so it must be classified next. Advance mid there and you skip an element, and the array comes out almost sorted — the worst kind of wrong.

DRILL 02 · RECALL

Merge Intervals sorts by start time. Why not by end time?

Sorting by start gives you the property the whole sweep rests on: once you are at interval i, every earlier interval starts before it, so if it overlaps anything it must overlap the most recently kept one. That turns “does this overlap any of the others?” — an all-pairs question — into a single comparison against out.back(). Sorting by end can also be made to work, but it is the natural key for a different problem: maximum non-overlapping intervals.

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
40 / DRILL UNIT 05 · Sorting As A Tool · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · BUG

Merge Intervals. On [[1,3],[2,6],[8,10],[15,18],[16,17]] this returns [[1,6],[8,10],[15,17]] instead of [[1,6],[8,10],[15,18]]. Which line?

if (!out.empty() && cur[0] <= out.back()[1])
    out.back()[1] = cur[1];
else
    out.push_back(cur);

max(out.back()[1], cur[1]). [16,17] sits entirely inside [15,18], so it overlaps — but assigning its end blindly pulls 18 back to 17 and silently loses an hour of coverage. It never crashes and the output still looks like a plausible list of merged intervals, which is what makes it expensive. Step the visualiser to the last interval and watch the kept bar deliberately not shrink.

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
41 / MECHANISM UNIT 05 · DUTCH · CODE MIRRORED

DUTCH NATIONAL FLAG — ONE PASS, THREE REGIONS

Three pointers carve the array into 0s done · 1s done · never looked at · 2s done. The move that everyone gets wrong is the last one: when a 2 goes to the back, mid does not advance — the value dragged in from high has never been classified. Step it and watch mid stall.

THE SAME EIGHT VALUES, EVERY ALGORITHM
CODE MIRROR
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
42 / MECHANISM UNIT 05 · INTERVALS · CODE MIRRORED

MERGE INTERVALS — THE SORT IS THE SETUP, NOT THE ANSWER

Unsorted, any interval can overlap any other and you are looking at O(n²) pairs. Sorted by start, an overlap can only ever be with the interval you just kept — so one comparison per interval settles it. That collapse from all-pairs to one sweep is what sorting actually bought you.

THE SAME EIGHT VALUES, EVERY ALGORITHM
CODE MIRROR
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
43 / PROBLEM #09 · AS-A-TOOL · MED

Sort Colors

MED as-a-tool ▶ SOLVE ON LEETCODEYou solved this in Arrays 1. Here it is the worked example for partitioning, which quick sort then reuses.
SIGNAL — WHAT GIVES IT AWAY

Only three distinct values, and the follow-up asks for one pass with constant space. Three buckets plus “one pass” is the Dutch national flag, every time. The counting-sort answer is the one the interviewer expects you to give first and then beat.

INTUITION

Maintain three regions with three pointers: everything before low is 0, everything between low and mid is 1, everything after high is 2, and the stretch from mid to high is unexamined. Look at a[mid] and send it to the region it belongs in, shrinking the unknown stretch by one each time — except when it goes to the back, because then you have pulled in something you have never seen.

STEPS
  1. low = 0, mid = 0, high = n − 1
  2. While mid <= high, examine a[mid]
  3. If 0: swap with a[low], then low++ and mid++
  4. If 1: it is already in the middle region, just mid++
  5. If 2: swap with a[high], then high−− and leave mid ALONE
  6. Stop when mid passes high — the unknown region is empty
↕ SCROLL
// Dutch national flag: three regions, one pass, no counting.
// The asymmetry between the two swaps is the whole problem.
void sortColors(vector<int>& a) {
    int low = 0, mid = 0, high = a.size() - 1;

    while (mid <= high) {
        if (a[mid] == 0) {
            swap(a[low], a[mid]);
            low++; mid++;               // safe: a[low] was a known 1
        } else if (a[mid] == 1) {
            mid++;                      // already in the middle region
        } else {
            swap(a[mid], a[high]);
            high--;                     // mid does NOT move -- the value
        }                               // from high has never been seen
    }
}
TIMEO(n)each element is classified once, in a single pass
SPACEO(1)three indices, no counts array
TRAP

Advancing mid after swapping a 2 to the back is the bug in this problem. Swapping with low is safe — the region before mid is already classified, so what comes back is known to be a 1. Swapping with high hands you a value from the unexamined region, and if you skip it you leave a 0 stranded on the right. The array comes back almost sorted, which is much harder to spot than a crash.

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
44 / PROBLEM #10 · AS-A-TOOL · MED

Merge Intervals

MED as-a-tool ▶ SOLVE ON LEETCODEYou solved this in Arrays 2. Here it demonstrates sorting as a tool rather than as the goal.
SIGNAL — WHAT GIVES IT AWAY

Intervals, and a question about overlap. Unsorted, overlap is an all-pairs question and therefore O(n²). The instant you are allowed to reorder them, sort by start and the question becomes local: does this one touch the last one I kept?

INTUITION

Sort by start time. Walk left to right holding a single “current” interval. Because the starts are ordered, no interval you have already passed can possibly reach further right than the one you are holding — so if the next interval overlaps anything at all, it overlaps that one. Extend it, or close it off and start a new one.

STEPS
  1. Sort the intervals by start time
  2. Push the first interval into the output
  3. For each remaining interval, compare its start with the last kept interval's end
  4. If start <= that end they overlap: extend the end to max(kept end, this end)
  5. Otherwise there is a real gap — push it as a new kept interval
  6. The output is already sorted, because the input was
↕ SCROLL
// Sorting is the SETUP here, not the answer: it makes "does this overlap
// anything?" into "does this touch the one interval I am holding?".
vector<vector<int>> merge(vector<vector<int>>& a) {
    sort(a.begin(), a.end());           // by start -- this is the whole trick
    vector<vector<int>> out;

    for (auto& cur : a) {
        if (!out.empty() && cur[0] <= out.back()[1])      // <= : touching counts
            out.back()[1] = max(out.back()[1], cur[1]);   // max: [16,17] inside
        else                                              // [15,18] must NOT
            out.push_back(cur);                           // shrink it
    }
    return out;
}
TIMEO(n log n)the sort dominates; the sweep afterwards is one linear pass
SPACEO(n)the output list — O(1) beyond it
TRAP

Writing kept.end = cur.end instead of max(kept.end, cur.end) breaks only when an interval is fully contained in the one you are holding — [15,18] then [16,17] comes back as [15,17]. It never crashes, it is still a valid-looking list of intervals, and most hand-written test cases miss it. Also use <=, not <: [1,3] and [3,5] touch at a point and almost every judge counts that as overlapping.

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
45 / INTRO UNIT 06 · Heap Sort

UNIT 06 — Heap Sort

A binary tree with no pointers and no allocation: the children of index i are 2i+1 and 2i+2, so a flat array is a heap if you agree to read it as one. That single reinterpretation buys you the guaranteed O(n log n) that quick sort cannot promise, in O(1) space that merge sort cannot promise.

THE QUESTION THIS LECTURE ANSWERS

WHAT IF THE ARRAY WERE ALREADY A TREE?

MAX HEAPHEAPIFYSIFT DOWNCOMPLETE TREEIN PLACE
WHAT TO WATCH FOR
  • 01CHILDREN OF i ARE 2i+1 AND 2i+2 — NO POINTERS EXIST ANYWHERE
  • 02BUILDING THE HEAP RUNS BACKWARDS FROM THE LAST PARENT, n/2−1
  • 03BUILD IS O(n), NOT O(n log n) — HE DERIVES THIS, AND IT SURPRISES PEOPLE
  • 04SORTING IS THEN JUST: SWAP ROOT TO THE END, SHRINK, SIFT DOWN
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
46 / VIDEO UNIT 06 · Heap Sort

Heap Sort — Heapify and Build Max Heap

ABDUL BARI
Heap Sort
RUNTIME 46:03
AFTER THIS → 3 DRILLS · NO SHEET ROW
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
47 / DRILL UNIT 06 · Heap Sort · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

Building a max heap from an unsorted array of n elements costs what?

O(n), and this is the result the lecture spends real time deriving because it looks wrong. Sifting down is only expensive for nodes near the top, and there are almost none of them: half the nodes are leaves and cost nothing, a quarter can sift at most one level, an eighth at most two. The sum ∑ n/2^(k+1) · k converges to n. Inserting one at a time genuinely is O(n log n) — the backwards build is what saves you.

DRILL 02 · TRACE

The array is [7, 2, 9, 4, 1, 8, 3, 6] read as a heap. Which elements are the children of index 1?

Indices 3 and 4, holding 4 and 1. The rule is 2i+1 and 2i+2, so index 1 has children at 3 and 4. Nothing about this array is a tree in memory — the tree exists entirely in the arithmetic, which is exactly why heap sort needs no extra space. Check the reverse too: the parent of index 4 is (4−1)/2 = 1.

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
48 / DRILL UNIT 06 · Heap Sort · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

Why does building the heap start at index n/2 − 1 rather than at 0 or at n − 1?

Everything from n/2 onward is a leaf, and a single node is already a valid heap — sifting it down does nothing. So the build starts at the last node that actually has children and walks backwards. Starting at 0 instead is not wrong so much as useless: sifting the root before its subtrees are heaps does not establish the property, which is why the direction has to be bottom-up.

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
49 / MECHANISM UNIT 06 · HEAP · CODE MIRRORED

HEAP SORT — THE ARRAY IS ALREADY A TREE

No pointers and no extra array: the children of index i are 2i+1 and 2i+2, so a flat array is a binary tree. Build a max heap, then repeatedly swap the root to the end and shrink. O(n log n) guaranteed — the worst case quick sort cannot promise.

THE SAME EIGHT VALUES, EVERY ALGORITHM
CODE MIRROR
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
50 / INTRO UNIT 07 · Counting Sort

UNIT 07 — Counting Sort

Every algorithm so far is stuck at n log n for one reason: they all ask is a < b?, and answering that question n log n times is provably unavoidable. This one never asks. It uses the value itself as an array index — and an index lookup is not a comparison, so the lower bound simply does not apply to it.

THE QUESTION THIS LECTURE ANSWERS

IS n log n REALLY A LOWER BOUND FOR SORTING?

COMPARISON LOWER BOUNDO(n + k)VALUE RANGESTABLEPREFIX SUM
WHAT TO WATCH FOR
  • 01THE VALUE BECOMES AN INDEX — counts[a[i]]++ — SO NO TWO VALUES ARE EVER COMPARED
  • 02THE COST IS O(n + k) WHERE k IS THE VALUE RANGE, NOT THE INPUT SIZE
  • 03IT DIES THE MOMENT k IS LARGE: SORTING THREE NUMBERS UP TO A BILLION IS ABSURD
  • 04THE PREFIX-SUM VERSION IS WHAT MAKES IT STABLE, AND RADIX SORT NEEDS THAT
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
51 / VIDEO UNIT 07 · Counting Sort

Counting Sort — Analysis and Code

ABDUL BARI
Counting Sort
RUNTIME 31:40
AFTER THIS → 3 DRILLS · NO SHEET ROW
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
52 / DRILL UNIT 07 · Counting Sort · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

Comparison sorts cannot beat O(n log n). How does counting sort get O(n + k)?

It is not a comparison sort at all, so the theorem simply does not cover it. The n log n bound comes from a decision-tree argument: with only yes/no comparisons you need at least log₂(n!) ≈ n log n of them to distinguish n! possible orderings. Counting sort sidesteps the whole argument by asking a different kind of question — where does this value live? — which an array index answers in O(1).

DRILL 02 · TRANSFER

You must sort 1000 integers, each between 0 and 1,000,000,000. Counting sort?

No. O(n + k) is linear in n + k, not in n, and here k dwarfs n by six orders of magnitude — you would allocate a billion-entry array to sort a thousand numbers. The rule to carry away: counting sort wins when k is comparable to n (ages, exam scores, the 0/1/2 of Sort Colors) and is a catastrophe otherwise. This is exactly the case radix sort was invented to rescue.

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
53 / DRILL UNIT 07 · Counting Sort · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

What makes the prefix-sum version of counting sort stable?

The prefix sums turn each count into “the position just past where this value's block ends”. Walking the input from the right and decrementing that position as you place each element means the last equal element is placed last, so the original order among equals survives. It matters far more than it looks: radix sort is only correct because its inner sort is stable, which is the next unit.

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
54 / MECHANISM UNIT 07 · COUNTING · CODE MIRRORED

COUNTING SORT — NOT ONE COMPARISON, ANYWHERE

Every algorithm above is stuck at n log n because they all ask is a < b?. This one never asks. It uses the value itself as an array index, so it escapes the comparison lower bound entirely — at the price of needing the values to be small integers.

THE SAME EIGHT VALUES, EVERY ALGORITHM
CODE MIRROR
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
55 / INTRO UNIT 08 · Radix Sort

UNIT 08 — Radix Sort

Counting sort's fatal flaw is a large value range. Radix sort fixes it by refusing to look at whole values at all: sort by the last digit, then the next, and so on. Each pass has a range of only ten — and the reason the whole thing works is that each pass is stable, so it preserves the order the previous pass established.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU KEEP COUNTING SORT'S SPEED WHEN THE VALUES ARE HUGE?

LSDDIGIT PASSBASE / BUCKETSSTABILITYO(d(n + b))
WHAT TO WATCH FOR
  • 01LEAST SIGNIFICANT DIGIT FIRST — THE OPPOSITE OF HOW YOU WOULD SORT BY HAND
  • 02EACH PASS IS A COUNTING SORT WITH k = 10, WHICH IS TINY AND FIXED
  • 03STABILITY IS NOT A NICETY HERE — IT IS THE ENTIRE CORRECTNESS ARGUMENT
  • 04COST IS O(d · (n + b)) FOR d DIGITS IN BASE b, NOT O(n log n)
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
56 / VIDEO UNIT 08 · Radix Sort

Radix Sort — Easiest Explanation with Code

ABDUL BARI
Radix Sort
RUNTIME 34:13
AFTER THIS → 3 DRILLS · NO SHEET ROW
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
57 / DRILL UNIT 08 · Radix Sort · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

Radix sort's per-digit pass must be stable. What breaks if it is not?

The whole thing collapses. After the units pass, two numbers with the same tens digit are already correctly ordered relative to each other. The tens pass sees them as equal — and if it is allowed to reorder equal elements, it throws away the work the units pass just did. Stability is what makes the passes compose rather than overwrite. That is why the previous unit's prefix-sum detail mattered.

DRILL 02 · RECALL

Why sort by the LEAST significant digit first rather than the most?

Going least-significant-first, every pass is a single flat stable sort over the whole array, and the result is correct after the last one — no recursion, no bookkeeping. Most-significant-first is not wrong, but once you have split on the top digit you must sort each bucket independently, which means recursion and separate sub-arrays. LSD is chosen because it is the flat, iterative version.

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
58 / DRILL UNIT 08 · Radix Sort · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · TRANSFER

Sorting 10⁶ integers up to 10⁹ in base 10. How does radix compare with an O(n log n) sort?

This is the honest answer and the one worth being able to give. d = 10 digits, so radix does 10 passes ≈ 10⁷ element-moves; a comparison sort does about n log₂n ≈ 2 × 10⁷ comparisons. The same ballpark — and in practice std::sort often wins anyway because it is cache-friendly while radix scatters writes across buckets. “Linear” hides a constant factor of d, and d grows with the value range.

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
59 / MECHANISM UNIT 08 · RADIX · CODE MIRRORED

STABILITY IS NOT A NICETY — IT IS THE REASON IT WORKS

Counting sort run once per digit, starting with the least significant, which looks backwards until you see why. Each pass must be stable: values sharing a digit keep the order the previous pass gave them, so a later pass on a higher digit never destroys the work of the lower ones. Drop stability and the whole scheme collapses. Not one comparison between two values, anywhere.

THE SAME EIGHT VALUES, EVERY ALGORITHM
CODE MIRROR
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
60 / INTRO UNIT 09 · Shell Sort

UNIT 09 — Shell Sort

Insertion sort is superb on nearly-ordered input and terrible otherwise, because a value can only ever move one slot per shift. Shell sort's fix is almost impudent: run insertion sort on elements spaced a gap apart, so a badly placed value can leap most of the array in one move — then shrink the gap. The final pass is ordinary insertion sort, on input that is now nearly ordered.

THE QUESTION THIS LECTURE ANSWERS

CAN YOU MAKE INSERTION SORT'S BEST CASE HAPPEN ON PURPOSE?

GAP SEQUENCEh-SORTEDDIMINISHING INCREMENTn^1.25
WHAT TO WATCH FOR
  • 01EACH PASS IS JUST INSERTION SORT WITH j -= gap INSTEAD OF j--
  • 02THE LAST PASS ALWAYS HAS gap = 1, SO CORRECTNESS IS INSERTION SORT'S
  • 03THE EARLIER PASSES EXIST ONLY TO MAKE THAT LAST PASS CHEAP
  • 04THE GAP SEQUENCE DECIDES THE COMPLEXITY — AND HALVING IS NOT THE BEST ONE
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
61 / VIDEO UNIT 09 · Shell Sort

Shell Sort — Full Explanation with Code

ABDUL BARI
Shell Sort
RUNTIME 34:07
AFTER THIS → 3 DRILLS · NO SHEET ROW
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
62 / DRILL UNIT 09 · Shell Sort · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

What is shell sort actually doing on each pass?

Insertion sort, on gapped subsequences. The code is literally insertion sort with j-- replaced by j -= gap — that one change is the whole algorithm. With gap = 4 on eight elements you are insertion-sorting {0,4}, {1,5}, {2,6} and {3,7} independently. Seeing it as “insertion sort with a stride” makes it something you can reconstruct rather than memorise.

DRILL 02 · RECALL

Why do the large-gap passes help, when the final gap = 1 pass has to run anyway?

Insertion sort's cost is essentially the number of inversions, and its weakness is that each shift removes exactly one. A gap pass moves a value many positions in a single step, killing many inversions at once. By the time gap reaches 1 the array is nearly ordered, which is precisely insertion sort's O(n) best case — so shell sort is insertion sort that manufactures its own best case first.

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
63 / DRILL UNIT 09 · Shell Sort · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · TRAP

Shell sort is stable, like the insertion sort it is built from. True or false?

False, and it is a genuinely surprising loss. Each individual gapped pass is stable within its own subsequence, but the subsequences are interleaved: a value can be lifted over an equal value that sits in a different subsequence entirely, and no later pass restores the original order. Building a stable algorithm out of stable parts does not give you a stable whole — which is exactly why radix sort had to be so careful.

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
64 / MECHANISM UNIT 09 · SHELL · CODE MIRRORED

INSERTION SORT, GIVEN A HEAD START

Insertion sort is slow for one reason: a value can only move a single slot per swap. Shell fixes exactly that by comparing across a gap, so a badly-placed element travels a long way in one move. The gaps shrink, and the final pass at gap 1 is insertion sort — but on an array that is already nearly sorted, which is insertion's best case rather than its worst.

THE SAME EIGHT VALUES, EVERY ALGORITHM
CODE MIRROR
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
65 / COMPLEXITY DERIVED, NOT MEMORISED

WHY n log n — AND HOW TWO ALGORITHMS CHEAT IT

Every bound in this deck is derived, not memorised. Three arguments, and the third is the one worth carrying out of here.

01 · WHY THE QUADRATICS ARE n²

Pass 1 looks at n−1 elements, pass 2 at n−2, and so on to 1. That sum is n(n−1)/2 — the triangle number — which is n²/2 once you drop the constant.

It is fixed for selection because the scan never stops early. Bubble and insertion can finish in O(n) only because their inner loop has a data-dependent exit.

02 · WHY DIVIDE-AND-CONQUER IS n log n

Halving until you reach single elements takes log₂ n levels. Every level touches all n elements exactly once — merging them, or partitioning them.

log n levels × O(n) per level. Quick sort only reaches this when the pivot splits evenly; a degenerate pivot gives n levels instead of log n, and you are back to n².

03 · WHY n log n IS A FLOOR — AND ITS ESCAPE HATCH

n elements have n! possible orders. Each comparison is one yes/no, so k comparisons distinguish at most 2ᵏ cases. You need 2ᵏ ≥ n!, giving k ≥ log₂(n!) ≈ n log n.

Note what the proof assumes: that you only ever compare. Counting and radix sort never do — they use the value as an index. The theorem is untouched; it simply does not apply to them.

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
66 / TRAPS PLAUSIBLE, WRONG, AND SILENT

THE SIX THAT ACTUALLY BITE

Not one of these crashes. Every one returns a plausible array and a wrong answer — which is exactly what makes them cost an hour instead of a minute.

BUBBLE'S FLAG DECLARED OUTSIDE THE LOOP

Set true by the first swap and never reset, so the early exit can never fire and the O(n) best case silently reverts to O(n²). The answer stays correct, which is why nobody notices.

MERGE COPY-BACK WITHOUT THE low OFFSET

a[i] = temp[i] instead of a[low + i]. The first merge has low = 0 so a small test passes perfectly, and every later subarray lands at the front.

QUICK SORT ON SORTED INPUT

First-element pivot makes already-sorted data the worst case, not the best: O(n²) and a recursion n deep. Sorted input is common, so this is a real failure, not a theoretical one.

COUNT INVERSIONS ADDING 1

inv++ instead of inv += mid - i + 1 counts one per merge step rather than per pair. On small examples the two can even agree. The total also overflows a 32-bit int at n = 10⁵.

ADVANCING mid AFTER A 2-SWAP

In the Dutch flag, the value pulled back from high has never been classified. Skip it and a 0 is stranded on the right — the array comes back almost sorted.

MERGING INTERVALS WITHOUT max()

kept.end = cur.end breaks only for a fully contained interval: [15,18] then [16,17] returns [15,17]. Still a valid-looking list, and most hand-written tests miss it.

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
67 / RECALL RETRIEVAL, NOT RECOGNITION · 1 OF 2

NO LOOKING BACK — PULL IT FROM MEMORY

DRILL 01 · RECALL

A problem needs the array sorted AND guarantees no worse than O(n log n), on 10⁶ elements, with memory tight. Which do you reach for?

Heap sort is the only one that gives you both. Quick sort is usually fastest but can degrade to O(n²); merge sort has the guarantee but wants O(n) extra memory, which the constraint rules out; counting sort needs a small value range that nobody promised. This is the trade-off the whole deck exists to make automatic.

DRILL 02 · RECALL

Which pair of algorithms escapes the O(n log n) comparison lower bound entirely?

Counting and radix. The bound is a theorem about algorithms that learn only through is a < b?; both of these use the value itself as an index and never ask. That is not a loophole, it is a different model — and it is the single best idea in the topic, which is why the deck teaches it even though the sheet does not.

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
68 / RECALL RETRIEVAL, NOT RECOGNITION · 2 OF 2

NO LOOKING BACK — PULL IT FROM MEMORY

DRILL 01 · TRANSFER

A problem says: 10⁵ meeting times, report how many overlap. What is your first move?

Sort, then sweep. All-pairs on 10⁵ is 10¹⁰ and hopeless; sorting costs 10⁵ log 10⁵ and then makes overlap a local question — you only ever compare against the interval you are holding. That reframing is unit 05's whole lesson and it generalises far past intervals: sorting is often the setup, not the answer.

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
69 / CHEATSHEET REVISION SURFACE

ONE SCREEN, THE WHOLE DECK

The slide to reopen the night before: every algorithm in this deck, what it costs, and the sentence in the statement that selects it.

ALGORITHM
TIME
SPACE
REACH FOR IT WHEN
Selection sort
O(n²)
O(1)
Writes are expensive — at most n−1 swaps, ever
Bubble sort
O(n²) · O(n)
O(1)
Teaching, and detecting an already-sorted array
Insertion sort
O(n²) · O(n)
O(1)
Small or nearly-sorted ranges — what libraries fall back to
Merge sort
O(n log n)
O(n)
Stability required, or the data is a linked list
Quick sort
O(n log n) avg
O(log n)
In-place and fast in practice — randomise the pivot
Heap sort
O(n log n)
O(1)
A guarantee AND no extra memory — the only one with both
Counting sort
O(n + k)
O(k)
Values in a small fixed range; no comparisons at all
Radix sort
O(d(n + b))
O(n + b)
Large values, few digits — needs a stable inner sort
Shell sort
~O(n^1.25)
O(1)
In-place, no recursion, better than insertion on random data
Dutch flag
O(n)
O(1)
Exactly three distinct values, one pass
Sort then sweep
O(n log n)
O(n)
Overlap or pair questions — order makes them local
INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
70 / CLOSE STEP 02 · ONE DECK

NINE WAYS TO ORDER — DONE

Nine units, 10 problems, and one idea underneath all of it: n log n is a floor only for algorithms that ask “is a < b?” If you remember two things, make them that — and that sorting is very often the setup rather than the answer.

00%
OF THIS DECK SOLVED
ALL TOPICSSTEP 15 · GRAPHS →

Lectures 1–3 are Striver's A2Z course; lectures 4–7 are Abdul Bari's sorting series. Problem links are GeeksforGeeks practice and LeetCode.

INVARIANT · SORTING · QUADRATIC · DIVIDE · LINEAR · ONE DECK
■ TOO SMALL TO READ
1280×720
INVARIANT · STEP 02 · ONE DECK

This one needs a laptop

Not a preference — an arithmetic one. Every slide is a fixed 1280 × 720 stage: a graph animating beside the code that drives it, with the problems laid out two columns wide. It scales as a single piece, so on a screen this size the body text comes out around 4px tall.

Shrinking it further would not help, and rebuilding it to reflow would mean losing the thing that makes it worth reading.

YOUR SCREEN0 × 0
NEEDED1060 × 610
WHAT IS WAITING ON THE LAPTOP
WATCH IT RUN · THEN RUN IT FROM MEMORY
Your progress is saved per device, so anything you tick on the laptop will be waiting there.