Math & Geometry

Most "math" problems reward spotting a closed-form shortcut — binary exponentiation, digit peeling, or cycle detection — instead of simulating the obvious loop. Master a small toolkit and the pattern recognition becomes mechanical.

Topic guide7 problems
The unlock

A "math" problem is usually begging you to work one tiny case by hand until you spot the invariant or the closed-form pattern — and then either replace the loop with a log-time math shortcut, or simulate it carefully while reusing the input itself as your scratch space so you pay O(1) extra memory.

MENTAL MODEL Find the pattern on paper before you write a loop

The brute-force version of these problems is always obvious: multiply x by itself n times, touch every digit, simulate every step. The fast version comes from noticing some structure the brute force ignores — a symmetry, an algebraic identity, or the fact that a bounded process must eventually repeat.

So the first move is never to code. It's to do the smallest non-trivial instance by handand watch what actually happens: rotate a 3×3 grid on scratch paper, expand x^4 = (x^2)^2, reverse the digits of 12. The transformation you discover by hand is the algorithm.

The reframe → don't ask “how do I loop this?” Ask “what stays the same every step (the invariant), and is there a shortcut that skips most of the steps?”

SEE IT Rotate = transpose + reverse; pow = repeated squaring

Rotate Image.You will never derive “rotate 90°” by staring at index formulas. Do it by hand on a 3×3 and the two moves jump out — transpose (flip across the diagonal), then reverse each row:

Goal: rotate 90° clockwise (top row should become right column)

   original          transpose          reverse each row
   (read rows)       (swap a[i][j]      (left<->right)
                      with a[j][i])      = the answer

   1 2 3              1 4 7              7 4 1
   4 5 6     ──►      2 5 8     ──►      8 5 2
   7 8 9              3 6 9              9 6 3

Why transpose-then-reverse? Transpose flips across the main
diagonal (rows<->cols). That alone faces the wrong way, so
reversing each row mirrors it into the clockwise position.
Both steps mutate IN PLACE → O(1) extra space.

Pow(x, n). Write the exponent in binary and the O(n) loop collapses to O(log n) — square the base each step, multiply it into the answer only where a bit is 1:

Compute x^13.   13 in binary = 1101

  exp   bit  base (=x^(2^k))   result (multiply in on a 1-bit)
  ----  ---  ---------------   -------------------------------
  13     1   x                 result = 1 * x      = x^1
   6     0   x^2               (skip — bit is 0)    x^1
   3     1   x^4               result = x^1 * x^4   = x^5
   1     1   x^8               result = x^5 * x^8   = x^13
   0          --               done

13 = 8 + 4 + 1 = the 1-bits.  x^13 = x^8 · x^4 · x^1.
Each row SQUARES the base and HALVES the exponent:
4 steps instead of 13 multiplies → O(log n).
The smell test → if the obvious loop runs n times but the answer is built from powers of twoof something, there's almost always a halving / squaring shortcut hiding underneath.

HOW TO THINK The cold-start ladder — run this on any numeric problem

When a problem is about numbers, grids, or points rather than a collection you search, climb these four rungs in order:

  1. Work the smallest example by hand. Rotate a 2×2, compute pow(x, 3), reverse "12", spiral a 2×3. Watch what each value does — don't reach for code yet.
  2. Name the transformation or invariant. Say it in one sentence: “90° clockwise = transpose then reverse each row,” or result × base^expnever changes.” If you can't name it, keep working rung 1.
  3. Can I avoid extra arrays? Ask whether you can mutate in place, or stash bookkeeping inside the input — use the first row/column as marker flags (Set Matrix Zeroes), or rotate four cells at a time. That buys O(1) extra space.
  4. Is there a log-time math shortcut? Squaring halves the work (fast exponentiation); %10 / //10 peels digits; a deterministic process on a bounded integer must cycle (happy number).
solve(numeric problem):

    1. work the SMALLEST case by hand  # rotate a 2x2, pow(x,3),
       on paper, no code               #   reverse "12": what moved?

    2. name the transformation/invariant
       "90deg cw = transpose + reverse row"
       "result * base^exp stays constant"

    3. can I avoid extra arrays?        # mutate in place, or stash
       reuse the input as scratch:      #   markers in row0/col0
       first row/col as flags

    4. is there a log-time shortcut?    # squaring halves the work;
       halve the work with math         #   %10 // 10 peels digits;
                                        #   bounded process -> cycle

ENCODE STATE The input can be your scratch space

The recurring trick for hitting O(1) extra space is encoding bookkeeping inside the input you already have. The grid is sitting right there — borrow its margins as a notebook.

Set Matrix Zeroes in O(1) space: use row 0 and col 0 as
the "should this line be zeroed?" notebook — no extra arrays.

   1 1 1            mark: a[i][0] and a[0][j] = 0 when a[i][j]==0
   1 0 1     ──►    (one extra flag tracks col 0 itself)
   1 1 1

   after marking (top row + left col carry the flags):
   1 0 1            row 1 flagged (a[1][0]=0)
   0 0 1            col 1 flagged (a[0][1]=0)
   1 0 1

   apply flags inward, then handle row 0 / col 0 LAST.

The input IS the storage. The trick: only read a marker
AFTER you've finished writing markers, never during.
The one rule that keeps this correct → separate the write markers phase from the read markersphase. If you read a flag while you're still setting flags, you can't tell an original 0 from one you just wrote. Mark everything first, then apply, and handle row 0 / col 0 last.

SAY IT State the invariant out loud before you trust the loop

Every one of these algorithms has a single sentence you can say that, if true at the start of each iteration and preserved by the body, proves the loop correct. Say it before coding:

  • Fast exponentiation:result × base^expequals the final answer at every step.” Odd exp? Multiply one factor into result to restore it.
  • Digit reverse:rev holds the already-peeled digits in reversed order; abs holds the digits not yet peeled.”
  • Plus One: “everything to the right of index iis final; a carry of 1 is still pending.”
  • Happy Number:“a bounded deterministic sequence either reaches 1 or revisits a value — so detect the repeat.”
Failure mode →the bugs here are almost all boundary off-by-ones — the spiral that re-visits the last column, the rotate that swaps a cell back, the carry that forgets the all-nines case. A stated invariant turns “does this work?” into “does the body preserve the sentence?”

WHEN IT BREAKS Simulate carefully — the boundaries bite

When no shortcut exists you simply simulate — but careful simulation is its own skill. Spiral Matrix and Rotate Image are won or lost on boundary bookkeeping, not cleverness.

  • Spiral Matrix: keep four shrinking bounds top, bottom, left, right. After walking an edge, move that bound inward, and re-check top ≤ bottom and left ≤ right beforethe next pass so a thin leftover row/column isn't traversed twice.
  • Rotate Image: the four-way cyclic swap only loops over theouter half in each dimension — overshoot and you rotate cells back to where they started.
  • Negatives in digit loops: -123 % 10 is -3 in JavaScript, and Math.floor(-7 / 2) is -4, not -3. Work on Math.abs(n) and restore the sign at the end.
The discipline →before you submit, trace the smallest grid that has an odd dimension (a 3×3) and the all-nines digit case by hand. Those two inputs flush out the boundary bugs that random examples miss.

MNEMONIC Square the base, halve the power.

Square the base, halve the power.Fast exponentiation reads the exponent in binary: square the base each step, multiply it into the answer only on a 1-bit. O(log n) multiplies instead of n. The Visualizetab walks the exponent's bits.

PATTERN Spot the shortcut, not the simulation

The defining move in math problems is recognizing that a naive loop — multiply x by itself n times, process each digit, sum forever until a cycle — has a much faster closed form. The four most common shortcuts are:

  • Binary (fast) exponentiation. Square the base repeatedly and absorb the base only on odd exponents. Cuts O(n) multiplications to O(log n).
  • Digit manipulation. Peel digits with n % 10 / floor-divide by 10 to process or reconstruct a number without converting to a string.
  • Modular arithmetic & cycle detection.Any deterministic process on a bounded integer must eventually repeat. A hash set or Floyd's two-pointer detects the cycle in linear time.
  • Carry propagation. Adding to a number stored as a digit array just requires a right-to-left carry loop, with one extra slot prepended in the all-nines edge case.

KEY IDEA Why fast exponentiation works

The squaring identity → x^n = (x^2)^(n/2) when n is even, and x^n = x · x^(n-1) when n is odd. Applying this recursively (or iteratively) halves the problem size at every step, just like binary search.

After k squarings you have covered 2^k of the original exponent. So you only need log₂(n) squarings total.

COST The complexity wins

Naive multiply loop
O(n)
One multiply per exponent step.
Fast exponentiation
O(log n)
One squaring per bit of n.

Similarly, grade-school string multiplication runs O(m × n) for m- and n-digit inputs — painful, but unavoidable without FFT. Cycle detection on a numeric process costs O(n) time and O(1) extra space with Floyd (vs O(n) space for a hash set).

GEOMETRY Coordinate hashing & collinearity

Geometry problems on LeetCode rarely require a full computational geometry library. The two recurring tricks are:

  • Hash by coordinate pair. Store (x, y) as a string key or a nested map to count points or group by position in O(1) per lookup.
  • Fix one point, enumerate the rest.For "how many squares can you form from these points?" (Detect Squares), fix a diagonal and check whether the other two corners exist in the hash map. The diagonal fully determines the square.

Overflow is the geometry pitfall: distance-squared comparisons using integer coordinates can easily exceed 32-bit range — use Number (64-bit float) or promote to BigInt when products of coordinates appear.

RUN IT Square the base, halve the power

step 0 / 12
STARTCompute 3^13in O(log n) multiplies, not n. Read the exponent in binary (1101) from the low bit up. Square the base, halve the power.
State
1
result
3
base
13 = 1101₂
exp
resultbase (squared each step)exponent bits
slowfast

TRIGGERS When you see ___ → reach for ___

Reach for the math & geometry toolkit when the problem is about numeric structure — the digits of a number, the value of an expression, the positions of points — rather than a collection you search or sort. The tell is that simulating the obvious process would be correct but too slow.

"compute x^n efficiently" / implement pow(x, n)binary (fast) exponentiation — O(log n) iterative squaring
"process digits / reverse a number" without string conversionmod-and-floor digit loop: peel with % 10, drop with Math.floor(n / 10)
"detect a repeating process / cycle" on bounded integershash set (O(n) space) or Floyd two-pointer (O(1) space) cycle detection
"increment / add to a number stored as a digit array"right-to-left carry propagation; prepend 1 if all nines
"multiply two large numbers given as strings"grade-school per-digit multiplication into a result array, then trim leading zeros
"count shapes / squares from a set of points"hash map of point counts; fix a diagonal, check the two implied corners exist
"is this a happy number?" / any deterministic integer sequencecycle detection on the sequence — same Floyd / hash-set pattern

RED FLAGSWhen it's NOT this pattern

  • It's really a hashing or array problem.If the "math" is just indexing by value or counting occurrences, a plain hash map is the move — no modular arithmetic needed.
  • Brute force is fine for tiny constraints.If n ≤ 30 or the digit count is at most 4, the naive loop is fast enough. Fast exponentiation is only worth the code complexity when n can be 2³¹ − 1.
  • Watch 32-bit overflow assumptions. TypeScript numberis a 64-bit float (safe integers up to 2⁵³), so intermediate products don't silently wrap the way Java int does — but the problem statement may still ask you to return 0 on 32-bit overflow, requiring an explicit range check.
  • Floored vs truncated division for negatives. Math.floor(-7 / 2) is -4, not -3. When peeling digits from a negative number, work with the absolute value and restore the sign at the end.

TEMPLATE Fast / binary exponentiation

When → Any pow(x, n) variant. Handle negative n upfront by inverting the base; then iterate, squaring the base and halving the exponent each step.

fast-binary-exponentiation.ts
function myPow(x: number, n: number): number {
  // Handle negative exponent: x^-n = (1/x)^n
  if (n < 0) { x = 1 / x; n = -n; }

  let result = 1;
  let base   = x;
  let exp    = n;           // iterative so stack is O(1)

  while (exp > 0) {
    if (exp % 2 === 1) result *= base;  // odd exponent: absorb one factor
    base *= base;                        // square the base each step
    exp  = Math.floor(exp / 2);          // halve the exponent (floored)
  }
  return result;            // O(log n) multiplications
}
Key invariant → result × base^exp equals the final answer at every iteration. When exp is odd, multiply result by base to restore the invariant before halving.

TEMPLATE Digit loop (peel &amp; rebuild)

When → Reverse a number, sum its digits, check digit properties. Work with Math.abs(n) to avoid negative-division surprises; re-apply the sign after.

digit-loop-peel-amp-rebuild-.ts
function reverseDigits(n: number): number {
  // Works for negatives: keep sign, reverse magnitude
  const sign = n < 0 ? -1 : 1;
  let abs = Math.abs(n);
  let rev = 0;

  while (abs > 0) {
    const digit = abs % 10;              // peel the last digit
    rev = rev * 10 + digit;              // shift result left, append digit
    abs = Math.floor(abs / 10);          // drop the last digit (floored)
  }

  // 32-bit overflow guard (LeetCode Reverse Integer spec)
  const MAX32 = 2 ** 31 - 1;
  if (rev > MAX32) return 0;
  return sign * rev;
}
Overflow guard → compare the reversed integer against 2^31 - 1(or the problem's stated bound)before returning, not inside the loop.

TEMPLATE Cycle detection on a numeric process

When → The algorithm repeatedly applies a function to an integer (sum of squared digits, Collatz step, etc.) and you need to know whether it reaches a fixed point or loops. Use hash-set for simplicity; Floyd for O(1) space.

cycle-detection-on-a-numeric-process.ts
// Hash-set variant (simple, O(n) space)
function hasRepeatedState(start: number, next: (n: number) => number): boolean {
  const seen = new Set<number>();
  let cur = start;
  while (!seen.has(cur)) {
    seen.add(cur);
    cur = next(cur);
    if (isTerminal(cur)) return false;   // reached a known fixed point
  }
  return true;                           // revisited a state → cycle
}

// Floyd variant (O(1) space)
function floydCycle(start: number, next: (n: number) => number): boolean {
  let slow = next(start);
  let fast = next(next(start));
  while (slow !== fast) {
    if (isTerminal(slow) || isTerminal(fast)) return false;
    slow = next(slow);
    fast = next(next(fast));
  }
  return true;                           // pointers met → cycle exists
}

declare function isTerminal(n: number): boolean;
Happy Number shortcut → any non-happy number eventually enters the cycle containing 4. You can just check cur === 1 (happy) or cur === 4 (unhappy) without a general set.

TEMPLATE Carry propagation (plus-one / add-as-array)

When → A number is stored as a digit array (most-significant first). Increment it or add a small value by walking right-to-left and propagating the carry. The only tricky case is all nines.

carry-propagation-plus-one-add-as-array-.ts
function plusOne(digits: number[]): number[] {
  // Walk right-to-left, propagate carry
  for (let i = digits.length - 1; i >= 0; i--) {
    if (digits[i] < 9) {
      digits[i]++;
      return digits;                     // no carry needed, done early
    }
    digits[i] = 0;                       // this digit flips 9 → 0, carry continues
  }
  // All digits were 9 (e.g. [9,9,9] → [1,0,0,0])
  digits.unshift(1);
  return digits;
}
All-nines edge case → after the loop every digit is 0. The carry is still 1, so prepend it. digits.unshift(1) is O(n) but unavoidable — the array genuinely grows by one slot.

PITFALL Integer overflow — know when to guard

TypeScript number holds integers exactly up to 2^53 - 1, so products of two 32-bit values are safe. But some problems (Reverse Integer) explicitly ask you to return 0 if the result doesn't fit in a signed 32-bit integer. Check against 2^31 - 1 / -(2^31) after computing, not during.

PITFALL Negative exponents and negative digit inputs

For pow(x, n) with negative n: invert x and negate n before the main loop — do not try to handle it inside. For digit loops: always call Math.abs(n) first and track the sign separately.-123 % 10 is -3 in JavaScript, not 3.

PITFALL Floored vs truncated division for negatives

Math.floor(-7 / 2) returns -4 (towards negative infinity), while C++/Java integer division truncates to -3 (towards zero). In the fast-exponentiation loop, expis always non-negative after the initial negation, so this doesn't bite there — but it matters whenever you floor-divide a value that could be negative mid-algorithm.

PITFALL Off-by-one in carry / the all-nines case

The most common Plus One bug: returning early after incrementing the last non-nine digit but forgetting to handle the leading carry. Ensure the loop runs through index 0, sets that digit to 0, and then outside the loop you prepend 1. Doing the unshift inside the loop leads to off-by-one prepends.