INVARIANT · ARRAYS · COMPOSED, NOT NEW
01
00/08
01 / COVER STEP 03 · ARRAYS
INVARIANT · STEP 03 · DECK 2 OF 2
EIGHT HARD NO NEW IDEAS

Nothing in this deck is a new idea. Every one of these eight is two things from deck 1 stacked — 3Sum is sorting plus two pointers, Reverse Pairs is merge sort plus a counter, Max Product is Kadane with a second running value. Hard here means composed, and that is a much smaller thing to learn.

8Problems
7Patterns
8Units
9Lectures
← → ↑ ↓  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 · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
02 / START HERE READ THIS ONE FIRST

HOW THIS DECK WORKS

This is not a list of problems. It is 8 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.

8 PROBLEMS · EVERY ONE LINKS TO LEETCODE TO SOLVE

INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
03 / INDEX PRESS I FROM ANYWHERE

INDEX — ALL 8 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.

ARRAYS [HARD] · 08
SOLVED HAS A LEETCODE LINK
INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
04 / SIGNALS WHEN YOU SEE X, REACH FOR Y

EIGHT HARD ROWS, NO NEW IDEAS

Every card below names a pair of deck-1 patterns. That is the whole thesis of this deck: hard here means composed, and composition is a much smaller thing to learn than eight new tricks.

“FIND ALL UNIQUE TRIPLETS / QUADRUPLETS SUMMING TO t”

order inside the tuple is irrelevant, and duplicates must not repeat

SORT, FIX k−2 INDICES, CLOSE THE REST WITH TWO POINTERSO(nk−1) time · O(1) space
“COUNT PAIRS i < j WHERE a[i] > 2·a[j]”

a condition on PAIRS across the whole array, and n is 10⁵

COUNT INSIDE MERGE SORT — BOTH HALVES ARE ALREADY SORTEDO(n log n) time · O(n) space
“MORE THAN ⌊n/3⌋ TIMES”

the threshold caps how many answers can exist — here, at most two

TWO CANDIDATES, CANCEL IN PAIRS, THEN ALWAYS VERIFYO(n) time · O(1) space
“ONE NUMBER IS REPEATED AND ONE IS MISSING”

two unknowns, and the values are a permutation of 1..n

TWO EQUATIONS: THE SUM AND THE SUM OF SQUARES (OR XOR BY BIT)O(n) time · O(1) space
“MERGE ALL OVERLAPPING INTERVALS”

overlap is a relation between pairs, but only adjacency matters once sorted

SORT BY START, THEN ONE SWEEP CARRYING THE OPEN INTERVALO(n log n) time · O(1) extra
“MAXIMUM PRODUCT SUBARRAY” — NOT SUM

negatives flip the ordering, so the smallest value is a candidate for largest

KADANE CARRYING BOTH A RUNNING MAX AND A RUNNING MINO(n) time · O(1) space
INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
05 / CONSTRAINTS BEFORE YOU WRITE A LINE

READ THE ANSWER OFF THE CONSTRAINTS

The Hard rows are where the bound actually bites — 3Sum's brute force is legal at n = 300 and fatal at n = 3000. 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 orderings
20–25
O(2ⁿ)
subsets, bitmask DP, meet-in-the-middle
200–400
O(n³)
the 3Sum brute force — legal ONLY at this size
10³–10⁴
O(n²)
3Sum, and 4Sum's O(n³) at the low end
10⁵
O(n log n)
merge-counting, interval sweeps, sort-then-scan
10⁶–10⁸
O(n) · O(log n)
voting, Kadane, the sum/XOR identities

THE GOLD-EDGED ROWS ARE WHERE THIS DECK LIVES · 3SUM IS O(n²) BY DESIGN, NOT BY FAILURE TO OPTIMISE

INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
06 / ROADMAP WHAT YOU ARE ABOUT TO DO

THE WHOLE RUN, 8 UNITS

All 8 units. Unit 06 owns no sheet row — it is Count Inversions, kept because Reverse Pairs is unteachable without it. Deck 1's nine patterns are assumed throughout.

UNIT 01

Pascal's Three Questions

▶ 26:453 DRILLS1 PROBLEM
UNIT 02

Voting, Generalised

▶ 26:583 DRILLS1 PROBLEM
UNIT 03

k-Sum: Sort, Fix, Shrink

▶ 38:254 DRILLS2 PROBLEMS
UNIT 04

Sort by Start, Then Sweep

▶ 22:353 DRILLS1 PROBLEM
UNIT 05

Two Unknowns, Two Equations

▶ 42:243 DRILLS1 PROBLEM
BEYOND THE SHEETUNIT 06

Counting Inside the Merge

▶ 24:173 DRILLSNO SHEET ROW
UNIT 07

Reverse Pairs

▶ 32:263 DRILLS1 PROBLEM
UNIT 08

Kadane Under Multiplication

▶ 20:273 DRILLS1 PROBLEM
INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
07 / WARMUP DECK 1 IS ASSUMED FROM HERE · 1 OF 2

THREE THINGS DECK 1 SHOULD HAVE LEFT YOU

DRILL 01 · RECALL

Every problem in this deck is a composition of two patterns from deck 1. Which pair does 3Sum stack?

Sorting + two pointers. And the order matters: the sort is not a tidying step, it is what creates the property the pointers need. On an unsorted array, a sum that is too small tells you nothing about which pointer to move — either could help. Once sorted, only one of them can, every single time. Read every hard problem in this deck as “which two things is this?” and they stop being memorisation.

DRILL 02 · RECALL

4Sum sums four values that each fit in an int. Why is long long not optional?

It wraps to a negative number. LeetCode 18 allows values up to 10⁹, so four of them reach 4×10⁹ — past INT_MAX at ~2.1×10⁹. Signed overflow wraps to something negative, which then compares as less than the target, so the pointer moves the wrong way and you either miss quadruplets or record ones that do not sum correctly. No crash. A plausible, wrong, shorter answer. Accumulate in long long and the whole class of failure disappears.

INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
08 / WARMUP DECK 1 IS ASSUMED FROM HERE · 2 OF 2

THREE THINGS DECK 1 SHOULD HAVE LEFT YOU

DRILL 01 · BUG

This is 3Sum's duplicate handling after a hit. It returns triplets that are all individually correct, and the judge still rejects it. Which line?

else {
    out.push_back({a[i], a[lo], a[hi]});
    lo++; hi--;                     // advance past the hit
}

Moving one slot is not enough. With [-2, 0, 0, 2, 2], after recording [-2, 0, 2] the pointers land on the second 0 and the second 2 — which sum identically, so the same triplet is recorded again. You need while (lo < hi && a[lo] == a[lo-1]) lo++; and its mirror for hi. Three duplicate skips exist in 3Sum — one for the fixed index and one for each pointer — and the fixed-index one is the only one most people remember.

INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
09 / INTRO UNIT 01 · Pascal's Three Questions

UNIT 01 — Pascal's Three Questions

Pascal's Triangle looks like three different problems wearing one name, and the sheet quietly asks all three: print the whole triangle, print one row, or find a single entry. The unlock for every version is the same and it is worth more than the triangle itself — the binomial coefficient rolls. nCk = nC(k−1) × (n−k+1) / k, so each entry is built from its left neighbour with one multiply and one exact divide. No factorial is ever formed.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU FIND ROW n OF PASCAL'S TRIANGLE WITHOUT COMPUTING A SINGLE FACTORIAL?

BINOMIAL COEFFICIENTTHE ROLLING nCr1-INDEXED VS 0SYMMETRYO(n) PER ROW
WHAT TO WATCH FOR
  • 01THE ROLL nCk = nC(k-1) × (n-k+1) / k IS THE ONE IDEA — EVERYTHING ELSE FOLLOWS
  • 02THE DIVISION IS ALWAYS EXACT, SO INTEGER ARITHMETIC NEVER LOSES A REMAINDER
  • 03NOT FORMING n! IS WHAT KEEPS THE INTERMEDIATE VALUES SMALL AND OVERFLOW-SAFE
  • 04THE ROW IS SYMMETRIC — nCk = nC(n-k) — SO YOU COULD FILL ONLY HALF
INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
10 / VIDEO UNIT 01 · Pascal's Three Questions

Pascal's Triangle - nCr in Minimal Time

STRIVER A2Z
Pascal's Three Questions
RUNTIME 26:45
AFTER THIS → 3 DRILLS · PROBLEM #01
INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
11 / DRILL UNIT 01 · Pascal's Three Questions · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

Why compute a row with the rolling formula nCk = nC(k−1)·(n−k+1)/k instead of the direct n! / (k!·(n−k)!)?

It sidesteps overflow entirely. n! explodes past 64 bits by n = 21, even when the final nCk is tiny and perfectly representable. The rolling form only ever holds values on the order of the answer itself, and — the part people distrust — each division is exact: nC(k−1)·(n−k+1) is always divisible by k, because it is a product of k consecutive integers over k. So plain integer arithmetic loses nothing. Computing a small result via a huge intermediate is a recurring anti-pattern, and this is the clean way out of it.

DRILL 02 · TRACE

Building row 4 by rolling, you have nC2 = 6. What is nC3, and by what step?

nC3 = 6 × 2 / 3 = 4. The falling factor is (n−k+1) = (4−3+1) = 2 and the rising factor is k = 3. Note the order the visualiser uses — multiply first, then divide — so the intermediate 12 is divisible by 3 with no remainder. Row 4 comes out [1, 4, 6, 4, 1], and you can see the symmetry: nC3 = nC1 = 4. Step the MECHANISM slide in PREDICT mode and it asks you for each next entry.

INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
12 / DRILL UNIT 01 · Pascal's Three Questions · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · TRANSFER

The variant asks only for a single entry, C(n, k), for one row deep in the triangle. What is the cheapest correct approach?

Roll k times and stop. A single entry needs neither the triangle above it nor the rest of its row — you start at C(n,0) = 1 and apply the roll exactly k times, which is O(k) time and O(1) space. Building the whole triangle to reach one cell is O(n²), and the factorial formula reintroduces the overflow you just avoided. This is the third of Pascal's three questions, and recognising that all three share one mechanism is the entire point of treating it as a unit.

INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
13 / MECHANISM UNIT 01 · PASCAL · CODE MIRRORED

EACH ENTRY ROLLS FROM THE ONE BEFORE IT

You never form a factorial. Each entry is the previous one times the falling factor (n−k+1) over the rising factor k — and that division is always exact, so no fractions and no overflow on the way to a value that would. One row is O(cols), the whole triangle O(rows²), and it reads the same backwards because nCk = nC(n−k).

ONE TRACK, EVERY ALGORITHM
CODE MIRROR
INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
14 / PROBLEM #01 · CONSTRUCT · HARD

Pascal's Triangle

HARD construct ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

“Pascal's Triangle” is really three requests behind one name — the whole triangle, a single row, or one entry — and the constraints tell you which. What they never want is a factorial: the moment you see C(n, k) with n past ~20, the signal is roll the coefficient, because n! overflows long before C(n, k) does.

INTUITION

Each row is the binomial coefficients nC0, nC1, … nCn, and adjacent coefficients are related by a clean roll: nCk = nC(k−1) × (n−k+1) / k. So start every row at 1 and multiply-then-divide across it. The division is exact because you are dividing a product of k consecutive integers by k, so integer arithmetic never drops a remainder, and no intermediate ever approaches the size of a factorial.

STEPS
  1. Every row begins with 1 — that is nC0 and needs no computation
  2. Carry the current coefficient; for k from 1 to n, multiply by (n−k+1)
  3. Then divide by k — this is exact, so plain integer division is correct
  4. Append each rolled value to the row
  5. For the whole triangle, repeat for each row 0..numRows−1
  6. For a single entry C(n,k), roll k times from 1 and stop — O(k), O(1) space
BRUTEO(n·k) with factorials / overflow-prone
OPTIMALO(numRows²)
↕ SCROLL
// The binomial coefficient ROLLS: nCk = nC(k-1) * (n-k+1) / k.
// No factorial is ever formed, so nothing overflows on the way to a value
// that would, and the division is always exact.
vector<vector<int>> generate(int numRows) {
    vector<vector<int>> tri;
    for (int n = 0; n < numRows; n++) {
        vector<int> row = {1};                  // nC0 is always 1
        long long v = 1;
        for (int k = 1; k <= n; k++) {
            v = v * (n - k + 1) / k;            // multiply then divide, exact
            row.push_back((int)v);
        }
        tri.push_back(row);
    }
    return tri;
}
TIMEO(numRows²)each of the ~n²/2 entries is one multiply and one divide
SPACEO(1) extrabeyond the output triangle itself, only a running coefficient
TRAP

Reaching for n! / (k!·(n−k)!). It is the textbook formula and it overflows a 64-bit integer at n = 21, returning a garbage coefficient for a value that would have fit in a byte. The rolling form never holds anything larger than the answer. The quieter trap is dividing before multiplying — v / k * (n−k+1) truncates, because v alone need not be divisible by k; only the product is. Multiply first, always.

INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
SOLUTION #01 · PASCAL'S TRIANGLE

Pascal's Triangle - nCr in Minimal Time

The walkthrough for #01 Pascal's Triangle. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
Pascal's Triangle - nCr in Minimal Time
RUNTIME 26:45
INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
15 / INTRO UNIT 02 · Voting, Generalised

UNIT 02 — Voting, Generalised

Boyer–Moore, generalised. The majority-element trick found one value occurring more than n/2 times; here the threshold is more than n/3, which caps the number of answers at two — you cannot have three distinct values each exceeding a third of the array. So carry two candidates and two counts. The cancellation still works, but a non-matching vote now cancels from both counts at once, and the order of the checks becomes load-bearing.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU FIND EVERY VALUE OCCURRING MORE THAN n/3 TIMES, IN O(1) SPACE?

n/3 THRESHOLDTWO CANDIDATESCHECK ORDERCANCEL FROM BOTHVERIFY
WHAT TO WATCH FOR
  • 01MORE THAN n/3 MEANS AT MOST TWO ANSWERS — WHICH IS WHY THERE ARE EXACTLY TWO SLOTS
  • 02THE EQUALITY CHECKS MUST COME BEFORE THE EMPTY-SLOT CHECKS, OR A CANDIDATE SEEDS THE OTHER SLOT
  • 03A VOTE THAT MATCHES NEITHER CANDIDATE CANCELS ONE FROM *BOTH* COUNTS
  • 04THE VERIFY PASS IS NON-NEGOTIABLE — 229 MAKES NO GUARANTEE THAT ANY ANSWER EXISTS
INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
16 / VIDEO UNIT 02 · Voting, Generalised

Majority Element II - Brute, Better, Optimal

STRIVER A2Z
Voting, Generalised
RUNTIME 26:58
AFTER THIS → 3 DRILLS · PROBLEM #02
INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
17 / DRILL UNIT 02 · Voting, Generalised · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

Why can there be at most two values occurring more than ⌊n/3⌋ times?

Three would overflow the array. If three distinct values each appeared strictly more than n/3 times, their combined count would be strictly more than n — but there are only n elements. So at most two can qualify, and that counting fact is why two candidate slots suffice. The same reasoning generalises: “more than n/k” admits at most k−1 answers and needs k−1 slots. The data structure size is dictated by the threshold, not guessed.

DRILL 02 · BUG

This checks the empty slots before the equality tests. On [1, 1, 2] it mishandles the second 1. What happens?

for (int v : a) {
    if (n1 == 0) { c1 = v; n1 = 1; }
    else if (n2 == 0) { c2 = v; n2 = 1; }
    else if (v == c1) n1++;
    else if (v == c2) n2++;
    else { n1--; n2--; }
}

The value 1 ends up in both slots. First 1 seeds c1. The second 1 should just increment n1 — but because the empty-slot check runs first and n2 is still 0, it seeds c2 = 1 too. Now a single value holds both candidacies and the algorithm can never track a genuine second majority. The fix is strict: test equality against existing candidates before ever filling an empty slot. It is the single ordering constraint in the whole method, and it is invisible until an early value repeats before a second distinct value appears.

INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
18 / DRILL UNIT 02 · Voting, Generalised · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · TRANSFER

After the vote you hold candidates 3 and 2. On the input the counts came out positive for both. Are you done?

Not done — verify both. The vote guarantees that any value exceeding n/3 is among your two survivors, but it never promises the survivors qualify. On [1, 2, 3] the vote proposes two candidates and the correct answer is the empty list. So re-scan, count each candidate's real occurrences, and keep only those strictly above ⌊n/3⌋. This is the same lesson as the n/2 case, sharpened: with n/3 there is no problem variant that guarantees an answer exists, so the verify pass is not optional even in principle.

INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
19 / MECHANISM UNIT 02 · VOTE2 · CODE MIRRORED

BOYER–MOORE, BUT WITH TWO SLOTS

“More than n/3” caps the answer at two values, so two candidates and two counts suffice. The subtle line is the order of the checks: the equality tests must come before the empty-slot tests, or a value equal to one candidate wrongly seeds the other. A non-matching vote cancels from both counts at once — and, as always with voting, the survivors are candidates until the verify pass confirms them.

ONE TRACK, EVERY ALGORITHM
CODE MIRROR
INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
20 / PROBLEM #02 · VOTING · HARD

Majority Element II

HARD voting ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

More than ⌊n/3⌋ times” is the whole signal, and it is doing two jobs at once: the fractional threshold says voting (cancel unequal elements), and the specific third caps the number of answers at two. Pair that with the near-universal follow-up — O(1) space, so no frequency map — and it is Boyer–Moore with two candidate slots.

INTUITION

Extend the majority-element vote to two candidates and two counts. Walk once: if the value matches a candidate, bump that count; otherwise, if a slot is empty, adopt the value there; otherwise it agrees with neither, so cancel one vote from both counts. After the pass the two survivors are the only possible answers — at most two values can exceed a third of the array — so a second pass counts their true frequencies and keeps only those that really clear the bar.

STEPS
  1. Hold two candidates and two counts; start the candidates distinct so neither matches spuriously
  2. For each value, FIRST test equality against candidate 1, then candidate 2
  3. Only if it matches neither, fill an empty slot, or if both are full, decrement both counts
  4. The order matters: equality tests must precede empty-slot seeding
  5. After the vote, the two candidates are the only possible >n/3 values
  6. Second pass: count each candidate's real occurrences; keep those strictly above n/3
BRUTEO(n) time · O(n) space, a hash map
OPTIMALO(n)
↕ SCROLL
// n/3 admits at most TWO answers, so two candidate slots. The equality
// tests must come BEFORE the empty-slot tests, or a repeated value seeds
// the second slot with itself. A non-match cancels from BOTH counts.
vector<int> majorityElement(vector<int>& a) {
    int c1 = 0, c2 = 1, n1 = 0, n2 = 0;         // distinct start values
    for (int v : a) {
        if (v == c1) n1++;                      // equality FIRST
        else if (v == c2) n2++;
        else if (n1 == 0) { c1 = v; n1 = 1; }
        else if (n2 == 0) { c2 = v; n2 = 1; }
        else { n1--; n2--; }                    // cancel from both
    }
    int f1 = 0, f2 = 0;                          // VERIFY -- 229 guarantees nothing
    for (int v : a) { f1 += (v == c1); f2 += (v == c2); }
    vector<int> out;
    if (f1 > a.size() / 3) out.push_back(c1);
    if (f2 > a.size() / 3) out.push_back(c2);
    return out;
}
TIMEO(n)two sequential linear passes
SPACEO(1)two candidates and two counters, whatever the input size
TRAP

Seeding the empty slots before testing equality. Put the n == 0 checks first and a value equal to candidate 1, arriving while slot 2 is still empty, seeds candidate 2 with that same value — one number holding both candidacies, and a real second majority can never be tracked. The other trap is the familiar one: skipping the verify pass. Unlike its n/2 cousin, this problem never guarantees an answer exists, so [1,2,3] must return the empty list, and only counting can tell you that.

INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
SOLUTION #02 · MAJORITY ELEMENT II

Majority Element II - Brute, Better, Optimal

The walkthrough for #02 Majority Element II. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
Majority Element II - Brute, Better, Optimal
RUNTIME 26:58
INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
21 / INTRO UNIT 03 · k-Sum: Sort, Fix, Shrink

UNIT 03 — k-Sum: Sort, Fix, Shrink

Two of deck 1's patterns, stacked. Sorting is not the answer here — it is the setup. An unsorted array tells you nothing when a sum comes out wrong: either pointer might fix it. A sorted array makes the choice forced, every single time, and that forcing is what collapses the O(n³) triple loop to O(n²). Everything hard about these two problems is then bookkeeping: which indices are fixed, and where the three duplicate skips go.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU FIND EVERY TRIPLET SUMMING TO ZERO WITHOUT CHECKING EVERY TRIPLET?

MONOTONICFIXED INDEXSHRINKING WINDOWDUPLICATE SKIPlong long
WHAT TO WATCH FOR
  • 01THE SORT IS O(n log n) AND THE SCAN IS O(n²) — SO SORTING IS EFFECTIVELY FREE
  • 02TOO SMALL MOVES lo; TOO BIG MOVES hi. THERE IS NEVER A CHOICE TO MAKE
  • 03THERE ARE THREE DUPLICATE SKIPS, NOT ONE — THE FIXED INDEX AND BOTH POINTERS
  • 04THE ACCUMULATOR IS long long IN 4SUM, AND THAT IS NOT PEDANTRY
INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
22 / VIDEO UNIT 03 · k-Sum: Sort, Fix, Shrink

3 Sum - Brute, Better, Optimal

STRIVER A2Z
k-Sum: Sort, Fix, Shrink
RUNTIME 38:25
AFTER THIS → 4 DRILLS · PROBLEM #03, #04
INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
23 / DRILL UNIT 03 · k-Sum: Sort, Fix, Shrink · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

The lecture insists the sort is what makes two pointers possible, not merely convenient. What breaks on an unsorted array?

The move stops being forced. On a sorted array, sum < target proves that moving hi left can only make things worse, so lo++ is the only justified action — and that is what lets you discard a whole region without examining it. Unsorted, either pointer might lead to the answer, so you cannot discard anything and you are back to checking all pairs. Two pointers is not a technique you apply to an array; it is a technique you apply to a monotonic one. Skipping duplicates gets easier too, but that is a bonus, not the reason.

DRILL 02 · TRACE

On the sorted array [-4, -1, -1, 0, 1, 2] with i fixed at index 0 (value −4), lo=1 and hi=5. The sum is −4 + −1 + 2 = −3. What happens next, and why is the other move wrong?

lo++. The sum −3 is below the target 0, so you need more. Because the array is sorted, a[hi] is the largest value available in the window — moving hi left can only make the sum smaller, taking you further from zero. The only way up is a larger a[lo]. Note the answer is not “2 is too big”: 2 is the best value you have, and the reason to keep it is the same monotonicity. Step the MECHANISM slide in PREDICT mode — it asks you exactly this at every comparison.

INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
24 / DRILL UNIT 03 · k-Sum: Sort, Fix, Shrink · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · BUG

Duplicate handling. Every triplet this emits is individually correct, and the judge still rejects the submission. Which line is at fault?

if (s == 0) {
    out.push_back({a[i], a[lo], a[hi]});
    lo++; hi--;                    // move past the hit
}

Advancing one slot is not the same as advancing past the duplicates. On [-2, 0, 0, 2, 2], after recording [-2, 0, 2] the pointers land on the second 0 and the second 2 — an identical sum, so the identical triplet is emitted again. You need while (lo < hi && a[lo] == a[lo-1]) lo++; and its mirror for hi. The problem says “unique” and that word is doing real work — it is the difference between a correct algorithm and an accepted submission.

DRILL 02 · TRANSFER

4Sum is “3Sum with one more loop”. Measured as a code diff it changes 10 lines, past this deck's 6-line ceiling for calling something a variant. What actually changes beyond the extra loop?

Four things travel with that loop. The target stops being a hardcoded 0 and becomes a parameter; lo anchors to j+1 rather than i+1; the new fixed index needs its own duplicate skip guarded by j > i+1 and not j > 0; and the sum needs long long because four values near 10⁹ overflow. This is why the deck refuses to render it as a diff: it is true as a sentence and false as a diff, and a 10-line diff would make two genuinely different implementations look interchangeable.

INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
25 / MECHANISM UNIT 03 · KSUM · CODE MIRRORED

SORT FIRST — THEN THE COMPARISON TELLS YOU WHICH POINTER MOVES

Sorting is not the answer here, it is the setup. Once the array is monotonic a single comparison decides everything: too small and only lo moving right can raise the sum; too large and only hi moving left can lower it. There is never a choice to agonise over, which is precisely what an unsorted array does not give you. Watch the duplicate skips too — they happen in three places, and missing any one of them produces a list that looks right until the judge diffs it.

ONE TRACK, EVERY ALGORITHM
CODE MIRROR
INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
26 / PROBLEM #03 · K-SUM · HARD

3Sum

HARD k-sum ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

Three words do all the work: “triplets” (so k−2 = 1 index gets fixed and the rest is a two-pointer close), “unique” (so duplicate skipping is part of the algorithm, not a tidy-up), and the absence of any mention of indices in the output — you return values, so you are free to sort. That last one is the permission slip for the entire approach.

INTUITION

Sort first. Now fix the smallest member of the triplet at index i and ask a much easier question of the tail: find two values summing to −a[i]. Because the tail is sorted, a running sum that is too small can only be fixed by moving lo right, and one that is too big only by moving hi left — so each comparison discards a whole region rather than a single pair. That is what turns O(n³) into O(n²).

STEPS
  1. Sort the array — this is what makes the pointer movement decidable
  2. Fix i from 0 to n−3; skip it when a[i] equals a[i−1] or every triplet repeats
  3. Set lo = i+1 and hi = n−1, closing in from both ends of the remaining tail
  4. Sum the three. Too small → lo++. Too big → hi--. Never both
  5. On a hit, record it, move BOTH pointers, then skip equal neighbours on both sides
  6. The outer loop stops at n−3 — a triplet needs two elements after i
BRUTEO(n³)
OPTIMALO(n²)
↕ SCROLL
// Sort is the SETUP, not the answer: it makes a wrong sum tell you
// which pointer to move. Unsorted, neither move would be justified.
vector<vector<int>> threeSum(vector<int>& a) {
    sort(a.begin(), a.end());               // monotonic buys the pointers
    int n = a.size();
    vector<vector<int>> out;

    for (int i = 0; i < n - 2; i++) {
        if (i && a[i] == a[i - 1]) continue;         // skip 1 of 3: fixed index
        int lo = i + 1, hi = n - 1;
        while (lo < hi) {
            long long s = (long long)a[i] + a[lo] + a[hi];
            if (s < 0) lo++;                // too small: only lo can help
            else if (s > 0) hi--;           // too big: only hi can help
            else {
                out.push_back({a[i], a[lo], a[hi]});
                lo++; hi--;
                while (lo < hi && a[lo] == a[lo - 1]) lo++;   // skip 2 of 3
                while (lo < hi && a[hi] == a[hi + 1]) hi--;   // skip 3 of 3
            }
        }
    }
    return out;
}
TIMEO(n²)one fixed index × a linear close of the tail; the sort is dominated
SPACEO(1)two indices — the output list is not counted as working space
TRAP

There are three duplicate skips and most people write one. The fixed index is the memorable one; the two after a hit are the ones that get forgotten, and they are why a submission whose every triplet is individually correct still fails — the judge compares lists, and yours has [-2,0,2] twice. The second trap is quieter: sorting is only legal because the output is values. The moment a problem asks for indices, sorting destroys the answer and this whole approach is off the table.

INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
SOLUTION #03 · 3SUM

3 Sum - Brute, Better, Optimal

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

SOLUTION WALKTHROUGH
3 Sum - Brute, Better, Optimal
RUNTIME 38:25
INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
27 / PROBLEM #04 · K-SUM · HARD

4Sum

HARD k-sum ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

Identical to 3Sum in every respect except one: the target is given rather than zero, and one more index gets fixed. Read “quadruplets” as “k = 4, so fix k−2 = 2 indices and close the last two with pointers”. The generalisation is the point — kSum is one algorithm with a loop depth of k−2.

INTUITION

Everything from 3Sum, one level deeper. Fix i, then fix j inside it, then close lo and hi against a target reduced by both fixed values. The only genuinely new hazard is arithmetic rather than algorithmic: four values near 10⁹ overflow a 32-bit accumulator and wrap negative, which makes the comparison lie and sends the pointer the wrong way.

STEPS
  1. Sort, exactly as in 3Sum
  2. Fix i from 0 to n−4, skipping when a[i] equals a[i−1]
  3. Fix j from i+1 to n−3, skipping when a[j] equals a[j−1] — but ONLY when j > i+1
  4. Set lo = j+1 and hi = n−1; the window is the tail after BOTH fixed indices
  5. Accumulate the four in long long, and compare against the given target
  6. On a hit, record, move both pointers, and skip equal neighbours on both sides
BRUTEO(n⁴)
OPTIMALO(n³)
↕ SCROLL
// kSum generalised: fix k-2 indices, close the last two with pointers.
// The accumulator is long long because four ints near 1e9 WRAP NEGATIVE,
// and a wrapped sum compares as "too small" and moves the wrong pointer.
vector<vector<int>> fourSum(vector<int>& a, int t) {
    sort(a.begin(), a.end());
    int n = a.size();
    vector<vector<int>> out;

    for (int i = 0; i < n - 3; i++) {
        if (i && a[i] == a[i - 1]) continue;
        for (int j = i + 1; j < n - 2; j++) {
            if (j > i + 1 && a[j] == a[j - 1]) continue;  // j > i+1, NOT j > 0
            int lo = j + 1, hi = n - 1;
            while (lo < hi) {
                long long s = (long long)a[i] + a[j] + a[lo] + a[hi];
                if (s < t) lo++;
                else if (s > t) hi--;
                else {
                    out.push_back({a[i], a[j], a[lo], a[hi]});
                    lo++; hi--;
                    while (lo < hi && a[lo] == a[lo - 1]) lo++;
                    while (lo < hi && a[hi] == a[hi + 1]) hi--;
                }
            }
        }
    }
    return out;
}
TIMEO(n³)two fixed indices × a linear close; the sort is dominated
SPACEO(1)four indices — the output list is not counted as working space
TRAP

j > i + 1, never j > 0. The inner duplicate skip must compare against the previous j within this i, and guarding with j > 0 throws away the very first valid j whenever it happens to equal a[i] — so quadruplets containing a repeated value silently vanish. The second trap is the int accumulator: four values near 10⁹ wrap negative, the sum then reads as below target, lo advances when it should not, and you get a short answer list on the large tests only.

INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
SOLUTION #04 · 4SUM

4 Sum - Brute, Better, Optimal

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

SOLUTION WALKTHROUGH
4 Sum - Brute, Better, Optimal
RUNTIME 28:47
INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
28 / INTRO UNIT 04 · Sort by Start, Then Sweep

UNIT 04 — Sort by Start, Then Sweep

Two of deck 1's ideas stacked: sort, then sweep. On its own, deciding whether any two of n intervals overlap is O(n²). But sort them by start and a hard fact appears — a new interval can only overlap the one you most recently kept, because every earlier kept interval starts no later and has already been closed off. So one pass carrying a single “current” interval merges everything, and the only line that bites is max() when you extend.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU MERGE OVERLAPPING INTERVALS WITHOUT COMPARING EVERY PAIR?

SORT-THEN-SWEEPOVERLAP TESTTHE max() RULEOPEN INTERVALO(n log n)
WHAT TO WATCH FOR
  • 01SORTING BY START IS THE SETUP — IT IS WHY ONLY THE LAST KEPT INTERVAL CAN OVERLAP
  • 02COMPARE THE NEW start AGAINST THE KEPT end — THAT ONE COMPARISON DECIDES MERGE OR SEPARATE
  • 03EXTEND WITH max(kept.end, new.end) — A SHORT INTERVAL INSIDE A LONG ONE MUST NOT SHRINK IT
  • 04THE SORT COSTS O(n log n) AND DOMINATES; THE SWEEP ITSELF IS O(n)
INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
29 / VIDEO UNIT 04 · Sort by Start, Then Sweep

Merge Overlapping Intervals

STRIVER A2Z
Sort by Start, Then Sweep
RUNTIME 22:35
AFTER THIS → 3 DRILLS · PROBLEM #05
INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
30 / DRILL UNIT 04 · Sort by Start, Then Sweep · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

After sorting by start, why is it enough to compare each interval against only the most recently kept one, rather than all kept intervals?

Sorted starts collapse the comparison. When you reach a new interval, every kept interval before the last began no later than the last one did — and if any of them still reached this far right, it would have merged with the last one already. So the last kept interval's end is the furthest-right boundary in play, and it is the only one a new interval can touch. That is the whole reason the sort turns O(n²) into O(n): it makes “which intervals could overlap” answerable by looking at exactly one.

DRILL 02 · BUG

When two intervals overlap, this sets the kept end to the new interval's end. On [[1,5],[2,3]] it returns [1,3]. What is wrong?

if (cur[0] <= last[1])
    last[1] = cur[1];            // extend the kept interval
else
    out.push_back(cur);

It needs max. When the incoming interval sits entirely inside the kept one — [2,3] inside [1,5] — its end (3) is smaller than the kept end (5), so assigning it directly shrinks the merged interval to [1,3] and silently drops coverage. last[1] = max(last[1], cur[1]) keeps whichever end reaches further. It is invisible whenever intervals happen to arrive in increasing-end order, which most hand-written examples do — the nested case is the one that catches it.

INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
31 / DRILL UNIT 04 · Sort by Start, Then Sweep · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · TRANSFER

A related problem asks for the total length covered by the union of the intervals. How does this template change?

The skeleton is identical. Sort by start, sweep once carrying the current open interval, and every time a block closes (a non-overlap forces a new interval, or you reach the end) add its end − start to a total. You never need to store the merged list at all. A great many interval problems — union length, count of merged groups, the largest gap — are this one sweep with a different accumulator, which is exactly why the sweep is worth owning as a pattern rather than memorising Merge Intervals as a one-off.

INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
32 / MECHANISM UNIT 04 · SWEEP · CODE MIRRORED

SORT BY START, THEN ONE SWEEP

Sorting is the setup, not the answer: once intervals are ordered by start, an overlap can only ever be with the interval you just kept, which collapses O(n²) pair-checking into a single pass. The one rule that bites is max() when extending — a short interval sitting entirely inside a long one must not shrink it.

ONE TRACK, EVERY ALGORITHM
CODE MIRROR
INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
33 / PROBLEM #05 · SWEEP · HARD

Merge Intervals

HARD sweep ▶ SOLVE ON LEETCODEReturns in Sorting, where the lesson is that the sort is the setup and the sweep is the answer.
SIGNAL — WHAT GIVES IT AWAY

“Merge all overlapping intervals” with intervals given unsorted. Overlap sounds like a pairwise relation — O(n²) — but the fix is the tell: whenever a problem is about intervals and order does not otherwise matter, sorting by start converts “which of these overlap” into “does this one touch the last one I kept”, and the whole thing becomes a single sweep.

INTUITION

Sort by start. Sweep left to right carrying one “current” interval. For each next interval, compare its start against the current interval's end: if it is less than or equal, they overlap, so extend the current end to the maximum of the two ends; otherwise there is a real gap, so close the current interval and start a new one. Sorted starts guarantee no earlier interval can reach past the current end, so comparing against just the last kept interval is sufficient.

STEPS
  1. Sort the intervals by start — the setup that makes the sweep valid
  2. Initialise the output with the first interval as the current open one
  3. For each subsequent interval, compare its start to the last kept interval's end
  4. If start <= kept end, they overlap: set kept end = max(kept end, this end)
  5. Otherwise push this interval as a new current one
  6. max() is essential — a short interval nested in a long one must not shrink it
BRUTEO(n²) pairwise overlap checks
OPTIMALO(n log n)
↕ SCROLL
// Sort by start, then ONE sweep: an overlap can only ever be with the
// interval you just kept, because sorted starts mean nothing earlier reaches
// further right. max() when extending -- a nested short interval must not shrink it.
vector<vector<int>> merge(vector<vector<int>>& iv) {
    sort(iv.begin(), iv.end());
    vector<vector<int>> out;
    for (auto& cur : iv) {
        if (out.empty() || cur[0] > out.back()[1])
            out.push_back(cur);                     // real gap: keep separate
        else
            out.back()[1] = max(out.back()[1], cur[1]);   // overlap: extend
    }
    return out;
}
TIMEO(n log n)the sort dominates; the sweep itself is a single O(n) pass
SPACEO(1) extrabeyond the output list, only a reference to the current interval
TRAP

Assigning kept.end = cur.end instead of max(...). When the incoming interval nests entirely inside the kept one, its end is smaller, so the direct assignment shrinks the merged interval and drops coverage — [[1,5],[2,3]] becomes [1,3]. It hides whenever ends happen to arrive in increasing order. The second trap is forgetting to sort, or sorting by end: the entire “only the last kept interval can overlap” guarantee rests on sorted starts, and without it the single-pass logic is simply wrong.

INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
SOLUTION #05 · MERGE INTERVALS

Merge Overlapping Intervals

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

SOLUTION WALKTHROUGH
Merge Overlapping Intervals
RUNTIME 22:35
INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
34 / INTRO UNIT 05 · Two Unknowns, Two Equations

UNIT 05 — Two Unknowns, Two Equations

One number in 1..n appears twice and one is missing. Two unknowns, so one equation cannot pin them down — you need two independent equations. The cleanest pair is the sum and the sum of squares. Each has a closed form for a perfect 1..n, so the differences between actual and expected give you dup − missing and dup² − missing², and a line of algebra separates the two. O(1) space, one pass, no array of seen-flags.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU FIND BOTH THE DUPLICATE AND THE MISSING NUMBER IN ONE PASS, O(1) SPACE?

TWO EQUATIONSSUM IDENTITYSUM OF SQUARESlong longO(1) SPACE
WHAT TO WATCH FOR
  • 01TWO UNKNOWNS DEMAND TWO EQUATIONS — ONE SUM IS NOT ENOUGH TO SEPARATE dup FROM missing
  • 02SUM DIFFERENCE GIVES dup - missing; SUM-OF-SQUARES DIFFERENCE GIVES dup² - missing²
  • 03DIVIDING THE SECOND BY THE FIRST GIVES dup + missing — THEN TWO LINEAR EQUATIONS SOLVE IT
  • 04THE ACCUMULATORS MUST BE 64-BIT — SUM OF SQUARES TO n=10⁵ IS FAR PAST INT RANGE
INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
35 / VIDEO UNIT 05 · Two Unknowns, Two Equations

Find the Missing and Repeating Number

STRIVER A2Z
Two Unknowns, Two Equations
RUNTIME 42:24
AFTER THIS → 3 DRILLS · PROBLEM #06
INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
36 / DRILL UNIT 05 · Two Unknowns, Two Equations · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

The sum difference alone gives dup − missing. Why is that not enough, and what does the sum of squares add?

One equation, two unknowns is underdetermined. Knowing dup − missing = 3 is consistent with (4,1), (5,2), (100,97) and endlessly more. The sum of squares supplies a second relation, dup² − missing² = (dup−missing)(dup+missing), and dividing it by the first difference hands you dup + missing directly. Now you have both the difference and the sum of the two unknowns, which a pair of linear equations resolves uniquely. Counting unknowns and matching them with equations is the whole method, and it generalises to any “k values wrong” variant.

DRILL 02 · TRACE

On [3, 2, 3, 4, 6, 5] (n = 6), the expected sum is 21 and expected sum of squares is 91. The actual sum is 23. What is dup − missing, and what is the duplicate?

dup − missing = 23 − 21 = 2, and the duplicate is 3. The actual sum of squares is 99, so the squares difference is 99 − 91 = 8, and 8 / 2 = 4 = dup + missing. Solving dup − missing = 2 with dup + missing = 4 gives dup = 3, missing = 1 — and indeed the array has two 3s and no 1. The MECHANISM slide accumulates both running totals and then walks the two-line solve.

INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
37 / DRILL UNIT 05 · Two Unknowns, Two Equations · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · TRANSFER

An alternative avoids the sum of squares entirely using XOR. What does XOR-ing all the values with all of 1..n give you, and what is still needed?

XOR gives dup ⊕ missing, then a partition finishes it. Fold every array value and every index 1..n into one accumulator; the correctly-present values cancel in pairs and you are left with dup ⊕ missing. Any bit set in that result is a bit where dup and missing differ, so partition all the numbers into those with that bit and those without — dup and missing fall into different buckets, and XOR-ing each bucket isolates them. It is O(1) space with no overflow risk at all, which is its advantage over sum-of-squares. Two genuinely different derivations of the same answer — worth knowing both, because an interviewer may block one.

INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
38 / MECHANISM UNIT 05 · SETMISMATCH · CODE MIRRORED

TWO UNKNOWNS NEED TWO EQUATIONS

One duplicate and one missing value are two unknowns, so one equation cannot pin them down. Walk once and accumulate both the sum and the sum of squares, each measured against the closed-form total for a clean 1..n. Their differences give dup − missing and dup² − missing², and dividing the second by the first yields dup + missing — two linear equations, solved in O(1) space.

ONE TRACK, EVERY ALGORITHM
CODE MIRROR
INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
39 / PROBLEM #06 · XOR-IDENTITY · HARD

Set Mismatch

HARD xor-identity ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

“Numbers 1..n, one is duplicated and one is missing, find both” — a complete known range with exactly two defects. That framing is the signal for an identity-based solution: because you know precisely what a clean array should sum and square to, the gaps between expected and actual pin down the two unknowns. The O(1)-space follow-up rules out the seen-array.

INTUITION

Two unknowns need two equations. Compute the running sum and running sum of squares of the array, and subtract the closed-form totals for a perfect 1..n. The first difference is dup − missing; the second is dup² − missing², which factors as (dup − missing)(dup + missing). Divide the second difference by the first to get dup + missing, and now two linear equations give both values immediately.

STEPS
  1. Compute S = (sum of array) − n(n+1)/2, which equals dup − missing
  2. Compute P = (sum of squares) − n(n+1)(2n+1)/6, which equals dup² − missing²
  3. Divide: P / S = dup + missing, since P = (dup−missing)(dup+missing)
  4. Now dup − missing and dup + missing are both known
  5. Solve: dup = (S + P/S) / 2, and missing = dup − S
  6. Use 64-bit accumulators — the sum of squares overflows int well before n = 10⁵
BRUTEO(n) time · O(n) space, a count array
OPTIMALO(n)
↕ SCROLL
// Two unknowns, two equations. Sum gives dup-missing; sum of squares
// gives dup^2-missing^2 = (dup-missing)(dup+missing). Dividing the second by
// the first yields dup+missing, and two linear equations finish it.
vector<int> findErrorNums(vector<int>& a) {
    long long n = a.size(), s = 0, s2 = 0;
    for (int v : a) { s += v; s2 += (long long)v * v; }

    long long d1 = s  - n * (n + 1) / 2;            // dup - missing
    long long d2 = s2 - n * (n + 1) * (2 * n + 1) / 6;  // dup^2 - missing^2
    long long sum = d2 / d1;                        // dup + missing
    int dup = (int)((d1 + sum) / 2);
    return {dup, (int)(sum - dup)};
}
TIMEO(n)one pass to build both sums
SPACEO(1)two 64-bit accumulators; no auxiliary array
TRAP

Accumulating in a 32-bit int. The sum of squares of 1..n is roughly n³/3, which passes INT_MAX by n ≈ 1800 — far below the constraint — so the running total wraps and every downstream value is garbage, with no crash. Use long long. The alternative XOR method sidesteps overflow entirely: XOR all values with all indices to get dup ⊕ missing, then split the numbers by a differing bit to separate them — worth knowing as a backup when an interviewer bars the arithmetic approach.

INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
SOLUTION #06 · SET MISMATCH

Find the Missing and Repeating Number

The walkthrough for #06 Set Mismatch. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
Find the Missing and Repeating Number
RUNTIME 42:24
INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
40 / INTRO UNIT 06 · Counting Inside the Merge

UNIT 06 — Counting Inside the Merge

This unit owns no sheet problem — it is the mechanism the next one is built on. Count Inversions asks how many pairs are out of order, and the answer piggybacks on merge sort: once the two halves are sorted, when you take a value from the right half before the left is exhausted, every remaining left value is larger, so each is an inversion with it — counted in bulk as len(L) − i rather than one at a time. That bulk count is the whole idea, and Reverse Pairs is a two-line variation on it.

THE QUESTION THIS LECTURE ANSWERS

HOW DO YOU COUNT OUT-OF-ORDER PAIRS FASTER THAN CHECKING EVERY PAIR?

INVERSIONMERGE SORTBULK COUNTCROSS-PAIRO(n log n)
WHAT TO WATCH FOR
  • 01THE COUNTING RIDES INSIDE THE MERGE — BOTH HALVES ARE ALREADY SORTED WHEN IT RUNS
  • 02TAKING A RIGHT VALUE EARLY ADDS len(L) − i INVERSIONS AT ONCE — THE BULK COUNT
  • 03EACH MERGE IS O(n) AND THERE ARE log n LEVELS, SO THE WHOLE COUNT IS O(n log n)
  • 04THE RECURSION SUMS THREE PIECES: INVERSIONS IN LEFT, IN RIGHT, AND ACROSS THE SPLIT
INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
41 / VIDEO UNIT 06 · Counting Inside the Merge

Count Inversions in an Array

STRIVER A2Z
Counting Inside the Merge
RUNTIME 24:17
AFTER THIS → 3 DRILLS · NO SHEET ROW
INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
42 / DRILL UNIT 06 · Counting Inside the Merge · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

During a merge, why can you count len(L) − i inversions in one step when you take a value from the right half?

The left half is sorted. If R[j] is smaller than L[i], then because everything from L[i] onward is ≥ L[i], all of them are also greater than R[j] — and each sits at an earlier original position, so each is a genuine inversion with R[j]. That lets you add all len(L) − i of them at once instead of comparing individually. Sortedness is what converts a per-pair count into a per-step count, which is the entire reason merge sort can count inversions in O(n log n).

DRILL 02 · TRACE

Merging left half [2, 3, 5] with right half [1, 4, 6], how many cross-inversions are counted, and at which steps?

Four. Taking R[0] = 1 first jumps it ahead of all three left values 2, 3, 5 — that is +3. Then 2 and 3 are taken from the left (no inversions), and taking R[1] = 4 jumps it ahead of the one remaining left value 5 — that is +1. Total 4, and the merged result is [1, 2, 3, 4, 5, 6]. Step the MECHANISM slide and it asks you, at each comparison, whether taking the right value adds inversions.

INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
43 / DRILL UNIT 06 · Counting Inside the Merge · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · TRANSFER

Why count inversions inside a merge sort at all, rather than with a simpler structure?

Merge sort gives the sortedness the count needs, for free. The bulk count only works because each half is already ordered when the merge runs, and merge sort produces exactly that ordering as a side effect of doing its job. So the counter rides along at no extra asymptotic cost. The other standard O(n log n) approach is a Binary Indexed (Fenwick) tree over value ranks, which trades the recursion for a running frequency structure — worth knowing both exist, because the merge version is easier to reason about and the Fenwick version generalises to online queries.

INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
44 / MECHANISM UNIT 06 · MERGECOUNT · CODE MIRRORED

COUNT IN BULK, BECAUSE THE HALVES ARE SORTED

Piggyback a counter onto merge sort. During a merge, the moment you take a value from the right half before the left half is exhausted, every remaining left value is larger — the left half is sorted — so each forms an inversion with it. That is +(len(L) − i) inversions counted in a single step, which is exactly why the whole thing is O(n log n) instead of the O(n²) of checking every pair.

ONE TRACK, EVERY ALGORITHM
CODE MIRROR
INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
45 / INTRO UNIT 07 · Reverse Pairs

UNIT 07 — Reverse Pairs

Reverse Pairs is Count Inversions with one word changed — the condition is a[i] > 2·a[j] instead of a[i] > a[j] — and that one word forces a real structural change. The doubled threshold does not line up with the merge's own comparison, so you cannot count while merging. Instead you count in a separate pass over the two sorted halves first, then merge normally. Because both halves are sorted, a single non-resetting pointer keeps that pass linear.

THE QUESTION THIS LECTURE ANSWERS

WHY CAN'T YOU COUNT REVERSE PAIRS DURING THE MERGE, THE WAY YOU COUNT INVERSIONS?

2× THRESHOLDSEPARATE COUNT PASSNON-RESETTING POINTERlong longO(n log n)
WHAT TO WATCH FOR
  • 01THE CONDITION a[i] > 2·a[j] DOES NOT MATCH THE MERGE'S a[i] <= a[j] COMPARISON
  • 02SO COUNTING IS A SEPARATE PASS, BEFORE THE MERGE, OVER THE TWO SORTED HALVES
  • 03THE j POINTER NEVER RESETS AS i ADVANCES — SORTEDNESS KEEPS THE PASS O(n)
  • 04USE 2LL·a[j] OR a[i] > 2·(long long)a[j] — DOUBLING NEAR 10⁹ OVERFLOWS int
INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
46 / VIDEO UNIT 07 · Reverse Pairs

Reverse Pairs - Hard Interview Question

STRIVER A2Z
Reverse Pairs
RUNTIME 32:26
AFTER THIS → 3 DRILLS · PROBLEM #07
INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
47 / DRILL UNIT 07 · Reverse Pairs · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

Why must reverse pairs be counted in a separate pass rather than during the merge, the way inversions are?

The two conditions are different relations. Counting inversions works during the merge because “a[i] > a[j]” is exactly the comparison the merge already makes, so each merge decision is a countable event. But a[i] > 2·a[j] is a stricter, different test — a value can satisfy the merge's ordering yet still be a reverse pair, or vice versa. So you sweep the halves once to count under the doubled condition, then merge them with the ordinary comparison. Two different relations need two separate walks.

DRILL 02 · TRACE

For sorted left [6, 9, 11] and right [1, 3, 10], the j pointer sweeps the right half. How many reverse pairs, and why does j never move backward?

Five. For L[0] = 6: 6 > 2·1 holds but 6 > 2·3 fails, so j stops at 1 → +1. For L[1] = 9: 9 > 2·3 = 6 holds, j advances to 2 → +2. For L[2] = 11: still 11 > 6, j stays at 2 → +2. Total 5. Because the left half is sorted, each larger L[i] can only satisfy the condition for more right values, so j only ever moves forward — which is what keeps the counting pass O(n) rather than O(n²).

INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
48 / DRILL UNIT 07 · Reverse Pairs · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · BUG

This counts with a[i] > 2 * a[j] in int arithmetic. It passes the samples and fails a hidden test. Why?

while (j <= hi &&
       a[i] > 2 * a[j])           // int overflow near 1e9
    j++;

2 * a[j] overflows. LeetCode 493 allows values up to 2³¹−1, so doubling one near that limit wraps to a negative number in 32-bit arithmetic — and then a[i] > (negative) is true for essentially everything, wildly over-counting on exactly the large inputs the samples do not include. Write a[i] > 2LL * a[j] so the doubling happens in 64 bits. Dividing instead — a[i] / 2 > a[j] — is the wrong fix: integer division truncates and drops genuine pairs, which is this deck's TRAP slide.

INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
49 / MECHANISM UNIT 07 · REVPAIRS · CODE MIRRORED

THE 2× BREAKS THE MERGE ORDER, SO COUNT SEPARATELY

Reverse Pairs asks for a[i] > 2·a[j], and that doubled threshold does not line up with the merge's ordinary comparison — so the counting is a separate pass before the merge. Both halves are sorted, so a single j pointer sweeps the right half as i advances and never resets: a larger a[i] can only push j further right, which keeps the pass O(n).

ONE TRACK, EVERY ALGORITHM
CODE MIRROR
INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
50 / PROBLEM #07 · MERGE-COUNT · HARD

Reverse Pairs

HARD merge-count ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

“Count pairs i < j with a[i] > 2·a[j]” at n ≤ 5×10⁴. Two things fire together: it is a count over pairs respecting original order (so a global sort is illegal — it would count pairs that never existed), and the bound forbids the O(n²) double loop. That combination is the signature of counting inside merge sort, where the sort happens but the count is taken while the halves are still positionally separated.

INTUITION

It is Count Inversions with the condition a[i] > 2·a[j]. Because the doubled threshold does not match the merge's own comparison, you count in a separate pass over the two sorted halves before merging: for each left value, advance a right pointer while a[i] > 2·a[j] and add how far it reached. The left half being sorted means that pointer never resets, so the pass is linear; then you merge normally and let the recursion sum the counts from every level.

STEPS
  1. Recurse: count reverse pairs in the left half and in the right half
  2. With both halves sorted, run a separate counting pass across the split
  3. For each i in the left half, advance j over the right while a[i] > 2·a[j]
  4. Add (j − start) to the count; j never resets because the left half is sorted
  5. Then merge the two halves normally with the usual comparison
  6. Return left count + right count + cross count; double in long long to avoid overflow
BRUTEO(n²)
OPTIMALO(n log n)
↕ SCROLL
// Count Inversions with the condition a[i] > 2*a[j]. That doubled test
// does NOT match the merge comparison, so count in a SEPARATE pass first,
// then merge normally. j never resets -> the pass is O(n). 2LL avoids overflow.
int countPairs(vector<int>& a, int lo, int mid, int hi) {
    int cnt = 0, j = mid + 1;
    for (int i = lo; i <= mid; i++) {
        while (j <= hi && a[i] > 2LL * a[j]) j++;   // 64-bit doubling
        cnt += j - (mid + 1);                       // j only moves forward
    }
    return cnt;                                     // then merge a[lo..hi] normally
}

int sortCount(vector<int>& a, int lo, int hi) {
    if (lo >= hi) return 0;
    int mid = (lo + hi) / 2;
    int c = sortCount(a, lo, mid) + sortCount(a, mid + 1, hi);
    c += countPairs(a, lo, mid, hi);
    merge(a, lo, mid, hi);                          // ordinary merge
    return c;
}
TIMEO(n log n)log n merge levels, each with an O(n) count pass and an O(n) merge
SPACEO(n)the temporary buffer merge sort needs
TRAP

Doubling in 32-bit arithmetic. 2 * a[j] with a[j] near 2³¹−1 overflows and wraps negative, so the comparison is true for almost everything and the count explodes — but only on the large values the samples omit. Write 2LL * a[j]. The tempting “fix” of comparing a[i] / 2 > a[j] is also wrong: integer division truncates, so 5 / 2 = 2 is not > 2 and a real pair (5, 2) goes uncounted. Keep the multiplication and widen it.

INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
SOLUTION #07 · REVERSE PAIRS

Reverse Pairs - Hard Interview Question

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

SOLUTION WALKTHROUGH
Reverse Pairs - Hard Interview Question
RUNTIME 32:26
INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
51 / INTRO UNIT 08 · Kadane Under Multiplication

UNIT 08 — Kadane Under Multiplication

“Maximum product subarray” looks like Kadane and refuses to be Kadane, for one reason: a deeply negative running product becomes the largest the instant it meets another negative. So a single running best is not enough — you have to carry both a running max and a running min ending at each position, and when the next value is negative you swap them, because multiplying by a negative turns the smallest into the largest. The running min is not bookkeeping; it is a live candidate for the answer.

THE QUESTION THIS LECTURE ANSWERS

WHY DOES MAXIMUM PRODUCT NEED A RUNNING MINIMUM WHERE KADANE NEEDED ONLY A MAX?

RUNNING MAX & MINSIGN FLIPTHE SWAPZERO RESETO(1) SPACE
WHAT TO WATCH FOR
  • 01A NEGATIVE VALUE SWAPS THE ROLES OF max AND min — SWAP THEM *BEFORE* MULTIPLYING
  • 02THE RUNNING MIN IS A CANDIDATE FOR THE MAX, NOT A SIDE QUANTITY
  • 03ZERO RESETS BOTH TO THE CURRENT VALUE — max(v, …) AND min(v, …) HANDLE IT FOR FREE
  • 04IT IS NOT A DIFF OF KADANE — THE LOOP BODY CARRIES TWO VALUES AND A CONDITIONAL SWAP
INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
52 / VIDEO UNIT 08 · Kadane Under Multiplication

Maximum Product Subarray - Best Intuition

STRIVER A2Z
Kadane Under Multiplication
RUNTIME 20:27
AFTER THIS → 3 DRILLS · PROBLEM #08
INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
53 / DRILL UNIT 08 · Kadane Under Multiplication · 1 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · RECALL

Why is a single running maximum, as in Kadane, insufficient for maximum product?

A negative times a negative is a big positive. Suppose the smallest product ending here is −48. On its own it is useless for a maximum — but if the next element is −2, then −48 × −2 = 96, likely the largest product you have seen, and it was only reachable through the minimum. So you must track the running min precisely so it is available to become the max one step later. In Kadane's world (sums) a negative running value can never help, so one variable suffices; under multiplication it can, which is the whole reason this is a distinct problem.

DRILL 02 · TRACE

On [2, 3, -2, 4], what is the maximum product, and where does the running min matter?

6, from [2, 3]. After the first two elements max is 6. At −2, max and min swap and then max becomes max(−2, 6·−2) = −2 while min becomes min(−2, 6·−2) = −12. At 4, max becomes max(4, −2·4) = 4 and the running min −12 stands ready in case a later negative flips it large. Here no later negative arrives, so the answer stays 6 — but the machinery to keep −12 around is exactly what a problem like [−2, 3, −4] (answer 24) depends on.

INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
54 / DRILL UNIT 08 · Kadane Under Multiplication · 2 OF 2

CHECK — CAN YOU ANSWER IT WITHOUT LOOKING BACK?

DRILL 01 · BUG

This multiplies before swapping on a negative. On [-2, 3, -4] it returns 12 instead of 24. Which line is misordered?

int v = a[i];
mx = max(v, mx * v);
mn = min(v, mn * v);
if (v < 0) swap(mx, mn);   // swap AFTER — too late

The swap has to precede the multiplications. When v is negative, the value that should seed the new max is the old min — so you must swap mx and mn first, then compute mx = max(v, mx·v) using the swapped values. Swapping afterward multiplies with the pre-swap max and min, so both new values are computed from the wrong predecessors and the −4 never combines with the stored −6 to make 24. The order is the algorithm: swap on sign, then extend.

INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
55 / MECHANISM UNIT 08 · MAXPROD · CODE MIRRORED

CARRY A MIN, BECAUSE A NEGATIVE FLIPS IT INTO THE MAX

Kadane cannot be reused directly: a deeply negative running product becomes the largest the instant it meets another negative. So carry both a running max and a running min ending here, and when the next value is negative, swap them first — the old min is about to become the new max. The running min is not bookkeeping; it is a live candidate for the answer.

ONE TRACK, EVERY ALGORITHM
CODE MIRROR
INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
56 / PROBLEM #08 · KADANE · HARD

Maximum Product Subarray

HARD kadane ▶ SOLVE ON LEETCODE
SIGNAL — WHAT GIVES IT AWAY

“Maximum product of a contiguous subarray” — the word product against the familiar sum is the entire signal. It looks like Kadane and is not, because sign matters: a negative flips the ordering of products, so the smallest running value is a candidate for the largest. Contiguous still means one pass and O(1) space, but the state you carry doubles.

INTUITION

Carry two running quantities ending at the current position: the maximum product and the minimum product. When the next element is negative, multiplying flips their roles, so swap max and min before extending. Then update each with max/min(v, running·v), which also handles a zero cleanly — it resets both to the current value. The answer is the largest max seen anywhere; the running min exists purely so it is ready to become the max after the next negative.

STEPS
  1. Initialise running max, running min, and best all to the first element
  2. For each later element v: if v is negative, swap the running max and min FIRST
  3. Update running max = max(v, runningMax · v)
  4. Update running min = min(v, runningMin · v)
  5. A zero makes both max(v,·) and min(v,·) select v, resetting the run
  6. Track best = max(best, running max) and return it
BRUTEO(n²)
OPTIMALO(n)
↕ SCROLL
// Not Kadane: a very negative product becomes the LARGEST the instant it
// meets another negative. So carry both a running max and min, and swap them
// BEFORE multiplying whenever v is negative. The min is a candidate, not noise.
int maxProduct(vector<int>& a) {
    int mx = a[0], mn = a[0], best = a[0];

    for (int i = 1; i < a.size(); i++) {
        int v = a[i];
        if (v < 0) swap(mx, mn);            // sign flip: swap FIRST
        mx = max(v, mx * v);                // zero resets both to v for free
        mn = min(v, mn * v);
        best = max(best, mx);
    }
    return best;
}
TIMEO(n)one pass, a constant amount of work per element
SPACEO(1)three integers, whatever the input size
TRAP

Swapping max and min after the multiplications instead of before. On a negative element the new max should be built from the old min, so the swap must precede both updates; swapping afterward computes both values from the wrong predecessors and quietly loses products like the 24 in [−2, 3, −4]. The second trap is initialising best = 0 — same mistake as Kadane, and it returns 0 on an all-negative single element such as [−3], where the answer is −3.

INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
SOLUTION #08 · MAXIMUM PRODUCT SUBARRAY

Maximum Product Subarray - Best Intuition

The walkthrough for #08 Maximum Product Subarray. Watch it, then go straight back and write it yourself.

SOLUTION WALKTHROUGH
Maximum Product Subarray - Best Intuition
RUNTIME 20:27
INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
57 / RECALL RETRIEVAL, NOT RECOGNITION

NAME THE TWO PATTERNS BEING STACKED

DRILL 01 · TRANSFER

“Count pairs i < j with a[i] > 2·a[j]”, n ≤ 5×10⁴. Why can this not be done with two pointers on the sorted array, the way 3Sum is?

Sorting destroys the index relation. 3Sum asks for values and does not care where they came from, so sorting is free. Reverse Pairs asks about pairs in original order, so a global sort would count pairs that do not exist. Merge sort threads the needle: it sorts, but it counts at each merge — at the moment the two halves are still positionally separated, so every pair counted genuinely has i in the left half and j in the right. That is why the counting must happen inside the recursion and not after it.

DRILL 02 · RECALL

Maximum Product Subarray tracks a running minimum alongside the maximum. What exactly does the minimum buy you?

Multiplying by a negative swaps the roles. If the running minimum is −48 and the next value is −2, the product is +96 — the largest thing you have seen, and it was reachable only through the minimum. So at every step you compute both candidates and, when a[i] < 0, swap them before extending. This is why Max Product is not a small edit of Kadane despite the family resemblance: the loop body carries two quantities and a conditional swap.

INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
58 / TRAPS PLAUSIBLE, WRONG, AND SILENT

SIX WAYS TO BE PLAUSIBLY WRONG

Every one of these compiles, runs, and returns something reasonable-looking. Four of them only fail on the large tests, which is the worst possible place to find out. A crash teaches itself; a silent wrong answer costs an hour.

THE UNVERIFIED VOTE, AT n/3

Majority II's two survivors are only the only possible answers, never proof that either occurs more than ⌊n/3⌋ times. On [1,2,3] the algorithm confidently proposes two candidates and the correct answer is the empty list. The second counting pass is not optional here — it is the algorithm.

SKIPPING DUPLICATES IN ONLY ONE OF THREE PLACES

3Sum needs a skip for the fixed index and for both pointers after a hit. Skipping only the fixed one emits duplicate triplets; skipping before recording loses valid ones. Every triplet you output is individually correct, so the bug looks like a judge problem.

int OVERFLOW IN THE 4SUM ACCUMULATOR

Four values near 10⁹ exceed INT_MAX and wrap negative, so the sum compares as below target and lo advances when it should not. You get a short answer list on the large tests only.

a[i] / 2 > a[j] IN REVERSE PAIRS

Dividing to avoid overflow truncates: with a[i]=5, a[j]=2, 5/2 = 2 is not > 2, so a genuine reverse pair goes uncounted. Compare a[i] > 2LL * a[j] instead. Off by a handful on big inputs — never on your examples.

COUNTING REVERSE PAIRS INSIDE THE MERGE LOOP

The count must run as a separate pass over the two halves before merging. Folding it into the merge comparison ties the counting pointer to the merging pointer, and the count comes out low. Both loops are O(n), so the separate pass costs nothing.

max() FORGOTTEN WHEN EXTENDING AN INTERVAL

last[1] = cur[1] looks right and shrinks the kept interval whenever a short interval sits entirely inside a long one — [15,18] then [16,17] yields [15,17]. Write max(last[1], cur[1]).

INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
59 / CHEATSHEET REVISION SURFACE

ONE SCREEN, THE WHOLE DECK

Seven compositions, eight problems. The right-hand column is the part that matters — by this point the algorithms are known and only the recognition is hard.

ALGORITHM
TIME
SPACE
REACH FOR IT WHEN
Sort + shrinking pointers (k-Sum)
O(n^(k−1))
O(1)
k values summing to a target — sorting is the setup, not the answer
Boyer–Moore, two candidates
O(n)
O(1)
“more than n/3” — at most two answers, and both must be verified
Count inside merge sort
O(n log n)
O(n)
count pairs across a split violating an order relation
Sum + sum of squares
O(n)
O(1)
two unknowns in a permutation of 1..n — two equations solve them
Sort by start, sweep
O(n log n)
O(1)
intervals — overlap can only be with the one you just kept
Kadane with a running min
O(n)
O(1)
maximum PRODUCT — a negative flips smallest into largest
Row-wise nCr
O(n²)
O(1) extra
Pascal's triangle — each entry from the previous by multiply-then-divide
INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
60 / CLOSE STEP 03 · DECK 2 OF 2

THE SHEET'S HARD ROWS, DONE

Nothing in this deck was a new idea, and that is the point worth carrying forward: hard problems are compositions. When the next one looks unfamiliar, the useful question is not “what trick is this” but “which two things is this”.

00%
OF THIS DECK SOLVED
← ALL TOPICS← DECK 1 · EASY + MEDIUMSTEP 15 · GRAPHS

Lectures are Striver's A2Z DSA course. Problem links are LeetCode. 8 units from 8 of the playlist's 28 lectures; rows 1–18 are deck 1.

INVARIANT · ARRAYS · COMPOSED, NOT NEW · DECK 2 OF 2
■ TOO SMALL TO READ
1280×720
INVARIANT · STEP 03 · DECK 2 OF 2

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.