Math and Geometry Problems
Math problems in interviews are rarely about mathematics. They are about avoiding the numeric traps, and there are three that recur constantly:
- Carry propagation. Digit-by-digit arithmetic where the answer can be longer than the input.
- Exponentiation. Multiplying
ntimes is ; squaring is — and negative exponents plus one asymmetric edge case are where submissions fail. - Floating point. Slopes, averages and divisions computed as floats give wrong answers, not just imprecise ones. The fix is almost always to compare with integer arithmetic instead.
That third point is the theme worth carrying away: when a problem seems to need division, look for a way to compare without dividing.
What you’ll learn
Section titled “What you’ll learn”- The carry loop, and why the “all nines” case needs a prepend.
- Exponentiation by squaring, driven by the bits of the exponent.
- Why slopes must be normalised integer pairs, never floats — with the concrete failure.
gcdas a canonicalisation tool, and the sign convention that makes it work.- Three real LeetCode problems solved in the browser: 66, 50, 149.
The cue
Section titled “The cue”Visual intuition
Section titled “Visual intuition”Most of this page is about avoiding a trap rather than running an algorithm, but one genuine
algorithm shows up constantly in the maths problems — the sieve. Watch the stride: the inner
loop jumps by i and starts at i², which is what separates it from a scan.
Two things worth pausing on. The inner loop starts at i² rather than 2i, because every smaller multiple of i has a factor below i and was crossed off in an earlier round. And the outer loop stops at √30, since any composite up to 30 must have a factor at or below its square root.
Trap 1 — carry propagation
Section titled “Trap 1 — carry propagation”def plus_one(digits):
for i in range(len(digits) - 1, -1, -1): # right to left
if digits[i] < 9:
digits[i] += 1
return digits # no carry: done
digits[i] = 0 # carry into the next place
return [1] + digits # all nines: 999 -> 1000The early return is the whole structure: the moment a digit is below 9 there is
nothing left to carry, so you stop. Only if the loop completes — meaning every
digit was a 9 — do you need a new leading digit.
Trap 2 — exponentiation by squaring
Section titled “Trap 2 — exponentiation by squaring”Computing x^n by multiplying n times is , which is operations at
LC 50’s limits. Squaring reduces it to by using the binary
representation of the exponent:
def my_pow(x, n):
if n < 0:
x, n = 1 / x, -n # x^-n == (1/x)^n
result = 1.0
while n:
if n & 1: # this bit of the exponent is set
result *= x
x *= x # x, x^2, x^4, x^8, ...
n >>= 1
return resultEach loop iteration squares x to represent the next power of two, and multiplies
it into the result only when that bit is set. multiplications.
Trap 3 — never compare slopes as floats
Section titled “Trap 3 — never compare slopes as floats”Three points are collinear when the slopes between them are equal. The obvious implementation divides:
slope = (y2 - y1) / (x2 - x1) # WRONGThis fails in two distinct ways:
- Vertical lines divide by zero.
- Floating point makes distinct slopes compare equal, or equal slopes compare
unequal. With coordinates up to , ratios like
1/3are inexact, and accumulated error makes two genuinely different lines look identical.
The fix is to keep the slope as a normalised integer pair:
from math import gcd
def slope_key(x1, y1, x2, y2):
dx, dy = x2 - x1, y2 - y1
g = gcd(dx, dy) # gcd(0, k) == abs(k), so this is safe
dx, dy = dx // g, dy // g
if dx < 0 or (dx == 0 and dy < 0): # canonical direction
dx, dy = -dx, -dy
return (dx, dy)| Problem | Naive | Better |
|---|---|---|
| Plus One | — | , one pass |
pow(x, n) | multiplications | by squaring |
| Max points on a line | triples | slope buckets per anchor |
Dry run
Section titled “Dry run”Carry propagation — plus_one([1, 2, 9]). Walk from the right, and stop at the first digit
below 9:
| index | digit | action |
|---|---|---|
| 2 | 9 | it is 9 → set to 0, carry continues |
| 1 | 2 | below 9 → increment to 3 and return immediately |
Result [1, 3, 0]. The early return is the point: the loop only runs while there is a carry, so
the common case costs one iteration, not n.
The all-nines case is the one that needs the line after the loop: [9, 9] → both become 0, the loop
ends with the carry still live, and the answer is [1] + digits = [1, 0, 0] — one digit longer
than the input. Forgetting that line returns [0, 0], which is the standard failure and the only
input that exposes it.
Exponentiation by squaring — my_pow(2.0, 10). 10 = 1010₂:
n | bit | x (squared each round) | result |
|---|---|---|---|
| 10 | 0 | 2 | 1 — bit clear, nothing multiplied in |
| 5 | 1 | 4 | 4 |
| 2 | 0 | 16 | 4 |
| 1 | 1 | 256 | 1024 |
Four iterations rather than ten multiplications, and — the two set bits pick
exactly and . For n = 2^31 the naive loop is two billion multiplications and this is 31.
- Negative exponents are handled once, up front:
x, n = 1/x, -n. Doing it inside the loop, or forgettingn = -n, gives an infinite loop rather than a wrong answer. n = -2^31is the edge case that overflows in fixed-width languages, because-(-2^31)does not fit. Python is immune, but it is worth naming.
Slopes as integer pairs — points (1,1), (2,2), (3,3), (1,2). Taking (1,1) as the
anchor:
| to | (dx, dy) raw | ÷ gcd | canonical key |
|---|---|---|---|
(2,2) | (1, 1) | (1, 1) | (1, 1) |
(3,3) | (2, 2) | (1, 1) | (1, 1) — same key, so collinear ✓ |
(1,2) | (0, 1) | (0, 1) | (0, 1) — vertical, no division by zero |
- The float version gets this right and still cannot be trusted.
1/3 == 2/6happens to hold in IEEE 754, but0.1 + 0.2 == 0.3does not — so “it worked on my test case” proves nothing. The integer pair is exact by construction, which is a different kind of correct. - The vertical line is free.
dx = 0produces the key(0, 1)with no special case, where the division form raisesZeroDivisionError. - Sign canonicalisation is not cosmetic. Without it
(1, 2)and(-1, -2)— the same direction — become different dictionary keys, and collinear points get split across two buckets.
Complexity
Section titled “Complexity”| Problem | Time | Space | Note |
|---|---|---|---|
| Plus One / add strings | extra | early return makes the common case | |
LC 50 pow(x, n) | iterative, recursive | naive is | |
| LC 204 Count Primes | per-number trial division is | ||
| Single primality test | do not build a sieve for one query | ||
| LC 149 Max Points on a Line | a slope map per anchor point | ||
| LC 48 Rotate Image | transpose, then reverse each row | ||
| LC 54 Spiral Matrix | beyond output | four shrinking boundaries | |
| LC 202 Happy Number | per step | fast/slow pointers on a functional graph | |
| LC 66/43 big-number arithmetic | / | digit arrays, because the value does not fit |
Two observations worth carrying:
- The / / distinctions are the whole game here. Trial division to , exponentiation in , a sieve in — each replaces a linear or exponential loop, and picking the wrong one is what makes these problems time out rather than fail.
- Geometry answers are usually and that is fine. LC 149 tries every point as an anchor, which is intended: with the quadratic is the expected solution, and reaching for something cleverer is a misread of the constraints.
The variant map
Section titled “The variant map”| Variant | The technique | Canonical problem |
|---|---|---|
| Increment a digit array | Carry loop + prepend on all-nines | 66 |
| Add / multiply as strings | Same carry logic, positional accumulation | 415 · 43 · 2 |
| Fast power | Square, driven by exponent bits | 50 · 372 |
| Integer square root | Binary search on the answer | 69 |
| Collinear points | Normalised integer slope pairs | 149 |
Trailing zeros of n! | Count factors of 5 | 172 |
| Count primes | Sieve of Eratosthenes | 204 |
| Convex hull / area | Cross products, never slopes | 587 · 963 |
Practice — real LeetCode problems
Section titled “Practice — real LeetCode problems”LC 66 — Plus One · Easy
Section titled “LC 66 — Plus One · Easy”Problem. Given a large integer as an array of its digits (most significant first, no leading zeros), increment it by one and return the resulting digit array.
Constraints. 1 <= len(digits) <= 100, 0 <= digits[i] <= 9, and the number
has no leading zeros.
Examples. [1,2,3] gives [1,2,4] · [4,3,2,1] gives [4,3,2,2] ·
[9] gives [1,0]
Editorial — approach, complexity, follow-ups
Increment the least significant digit and propagate a carry leftward. The instant a digit is below 9, adding one cannot carry, so you can return immediately.
Time , and for most inputs since it usually returns on the first digit. Space if mutating in place, for the all-nines case where a new list is created.
The test cases map to the distinct behaviours:
[1,2,3]— no carry at all; returns on the first iteration.[1,9,9]gives[2,0,0]— carry propagates partway, then stops.[9]and[9,9]— carry propagates all the way out, and the result is one digit longer. This is the case the finalreturnexists for.[0]gives[1]— the smallest input.
It is tempting to convert to an integer, add one, and split back into digits. In Python that works, since integers are unbounded — but it defeats the question, which exists precisely because the number may exceed native integer range in other languages. Mention it, then write the digit loop.
Follow-ups you should expect: “Add two digit arrays (LC 415)?” — the same carry
logic with two inputs, walking both from the right. “Multiply them (LC 43)?” —
accumulate result[i + j + 1] += a[i] * b[j] into a len(a) + len(b) buffer, then
normalise carries in one pass. “Plus k instead of one?” — add k to the last
digit and carry with divmod. “Digits stored least-significant-first?” — easier;
you append rather than prepend, which is exactly LC 2’s representation.
LC 50 — Pow(x, n) · Medium
Section titled “LC 50 — Pow(x, n) · Medium”Problem. Implement pow(x, n), which computes x raised to the power n.
Constraints. -100.0 < x < 100.0, -2^31 <= n <= 2^31 - 1, and either
n is non-zero or x is non-zero. The answer is within 10^-4 of the true value.
Examples. (2.0, 10) gives 1024.0 · (2.1, 3) gives 9.261 ·
(2.0, -2) gives 0.25
Editorial — approach, complexity, follow-ups
Write the exponent in binary. Since
, you only need the powers of x at
powers of two — obtained by repeated squaring — multiplied together wherever the
exponent has a set bit.
Time multiplications. Space iteratively (a recursive version is stack).
Test-case notes: (2.0, 0) and (1.0, 0) both give 1.0, from result being
seeded at 1.0 and the loop never running. (2.0, -2) gives 0.25 via the
reciprocal. (2.0, 1) is the single-bit case.
The recursive formulation is equally valid and some find it clearer:
half = myPow(x, n // 2), then half * half (times an extra x if n is odd).
Same complexity, stack.
Python’s built-in x ** n and pow(x, n) do this for you — say so, then implement
it, since the question is about the technique.
Follow-ups you should expect: “Modular exponentiation?” — add % MOD after
each multiplication; that is pow(x, n, mod) built in, and the basis of RSA and
of modular inverses. “Integer square root (LC 69)?” — binary search on the answer,
or Newton’s method. “Matrix power?” — the identical algorithm with matrix
multiplication, which computes Fibonacci in . “Why not multiply n
times?” — operations at the stated limits.
LC 149 — Max Points on a Line · Hard
Section titled “LC 149 — Max Points on a Line · Hard”Problem. Given points on a 2D plane, return the maximum number of points
that lie on the same straight line.
Constraints. 1 <= len(points) <= 300,
-10^4 <= xi, yi <= 10^4, and all points are distinct.
Examples. [[1,1],[2,2],[3,3]] gives 3 ·
[[1,1],[3,2],[5,3],[4,1],[2,3],[1,4]] gives 4 · [[1,1]] gives 1
Editorial — approach, complexity, follow-ups
Any line with at least two points contains some point as its “leftmost in iteration order”, so anchoring on every point in turn and grouping the later points by direction finds every line. The largest bucket plus the anchor is the answer.
Time — each of n anchors examines the remaining points, with
average dictionary work (plus a factor for gcd). Space .
Three decisions carry the correctness:
- Integer pairs, not floats. Dividing gives inexact values for ratios like
1/3; two different lines can then hash to the same float, or one line to two different floats. The normalised pair is exact. - Sign canonicalisation. Without it,
(1,2)and(-1,-2)are distinct keys for the same direction. Since only later points are considered, this matters less than in the all-pairs formulation — but[[1,1],[2,2],[-1,-1]]is exactly the case that exposes it: anchored at[1,1], the directions to[2,2]and[-1,-1]are(1,1)and(-2,-2), which normalise to(1,1)and(-1,-1). Only the sign fix merges them, giving3rather than2. gcd(0, k) == abs(k). Vertical lines normalise to(0, 1)and horizontal to(1, 0)automatically.[[0,0],[0,1],[0,2]]gives3with no special case.
Only dx == dy == 0 would break it (gcd(0,0) == 0 and the division raises), and
that requires two identical points — which the constraints forbid. If duplicates
were permitted you would count them separately and add them to every line through
that position.
The len(points) <= 2 guard handles the single-point case, where the loop body
never runs and best + 1 would still give 1 — but the explicit guard states the
intent.
Follow-ups you should expect: “Why not floats?” — the most likely question;
give the precision argument. “Handle duplicate points?” — count and add them
separately. “Better than ?” — no known general improvement; the problem is
3SUM-hard. “Are three points collinear?” — the cross product
(x2-x1)*(y3-y1) - (y2-y1)*(x3-x1) == 0, which is exact, division-free, and the
right primitive for convex-hull and area problems.
LeetCode problem set
Section titled “LeetCode problem set”Generated from the problem database, so each entry carries its sheet membership and reported companies. Tick them off as you go — progress is saved in this browser, and the Export button writes it to a file you can keep.
Work down the ladder. Tick each problem off as you go — progress is saved in this browser, and the Export button in the filter bar writes it to a file you can keep.
- 66Plus OneeasyEarly return on no-carry; prepend on all-nines
- 7Reverse Integermedium
- 43Multiply StringsmediumPositional accumulation into a `m + n` buffer, then carry
- 50Pow(x, n)mediumSquaring driven by exponent bits; mind `-2^31`
- 172Factorial Trailing ZeroesmediumCount factors of 5; never compute the factorial
- 204Count PrimesmediumSieve of Eratosthenes, marking from `i * i`
- 2013Detect Squaresmedium
- 149Max Points on a LinehardNormalised integer directions; never float slopes
- 233Number of Digit Onehard
Interview follow-ups
Section titled “Interview follow-ups”| They ask | What they’re checking | The answer |
|---|---|---|
| “Why not use floats for slopes?” | The central trap | Precision makes distinct slopes compare equal; normalised integer pairs are exact and handle vertical lines |
| “Why normalise the sign?” | Care | (1,2) and (-1,-2) are the same direction but different dict keys, splitting one line into two buckets |
“What about gcd(0, 0)?” | Edge awareness | It is 0 and would raise — only possible with duplicate points, which the constraints exclude |
| “Why squaring rather than repeated multiplication?” | Complexity | versus ; at that is decisive |
“What breaks with n = -2^31?” | Reading constraints | Negating it overflows 32-bit signed; Python is immune but the constraint is there for a reason |
| “Convert digits to an int instead?” | Understanding the point | Works in Python, defeats the exercise, and fails where integers are fixed-width |
| “Are three points collinear?” | The right primitive | The cross product — exact and division-free |
Edge-case checklist
Section titled “Edge-case checklist”- All nines (LC 66) —
[9],[9,9]; the answer grows a digit. - Partial carry —
[1,9,9]gives[2,0,0]. - Single digit zero —
[0]gives[1]. - Exponent zero (LC 50) — returns
1.0for anyx. - Negative exponent — take the reciprocal.
n = -2^31— the overflow case in fixed-width languages.- Base between 0 and 1 —
(0.5, 2)gives0.25; nothing assumesx > 1. - Single point (LC 149) — answer
1. - Vertical and horizontal lines —
[[0,0],[0,1],[0,2]]gives3; no division by zero. - Opposite-direction collinear points —
[[1,1],[2,2],[-1,-1]]gives3, the sign-canonicalisation test. - Negative coordinates — legal, and they are what make the sign convention necessary.
Self-check
Section titled “Self-check”-
Why represent a slope as a normalised integer pair rather than a float?
`1/3 == 2/6` happens to hold in IEEE 754, which is exactly why a passing test proves nothing — `0.1 + 0.2 == 0.3` is False. Exactness by construction is a different guarantee from 'it worked here'.
pch.quizShowAnswer
B — Because division by zero on vertical lines and floating-point inexactness both make comparisons unreliable — the reduced (dx, dy) pair is exact by construction — `1/3 == 2/6` happens to hold in IEEE 754, which is exactly why a passing test proves nothing — `0.1 + 0.2 == 0.3` is False. Exactness by construction is a different guarantee from 'it worked here'.
-
After dividing (dx, dy) by their gcd, why canonicalise the sign?
The vertical case needs the tie-break too: when dx == 0, force dy positive, or (0,1) and (0,-1) diverge.
pch.quizShowAnswer
B — Because (1, 2) and (-1, -2) are the same direction but different dictionary keys — without canonicalisation collinear points get split across two buckets — The vertical case needs the tie-break too: when dx == 0, force dy positive, or (0,1) and (0,-1) diverge.
-
In `plus_one`, which input exposes the missing line after the loop?
Every other input returns early from inside the loop, so the trailing line only ever runs on all-nines. That is why it is both easy to omit and easy to miss in testing.
pch.quizShowAnswer
B — All nines — [9,9] must return [1,0,0], one digit LONGER than the input; without the final `[1] + digits` it returns [0,0] — Every other input returns early from inside the loop, so the trailing line only ever runs on all-nines. That is why it is both easy to omit and easy to miss in testing.
-
Exponentiation by squaring computes 2^10 in four iterations. Where do the multiplications happen?
The unconditional squaring is what makes the base hold x^(2^k) at round k; the bits select which of those rungs get multiplied in. 4 × 256 = 1024.
pch.quizShowAnswer
B — Only on set bits of the exponent — 10 is 1010₂, so the result picks up x² and x⁸ while the base is squared unconditionally every round — The unconditional squaring is what makes the base hold x^(2^k) at round k; the bits select which of those rungs get multiplied in. 4 × 256 = 1024.
-
You need to know whether one number around 10^12 is prime. Sieve?
Matching the tool to the query count is the recurring decision on this page: √n for one, n log log n for all of them up to n.
pch.quizShowAnswer
B — No — trial division to √n is about 10^6 operations, whereas a sieve of that size cannot even be allocated. Sieves are for bulk queries — Matching the tool to the query count is the recurring decision on this page: √n for one, n log log n for all of them up to n.
-
LC 149 (max points on a line) is O(n²). Is that a problem?
Reading the constraints backwards to infer the intended complexity works here as elsewhere: n ≤ 300 rules out any need for cleverness beyond the per-anchor slope map.
pch.quizShowAnswer
B — No — trying every point as an anchor is the intended solution, and with n ≤ 300 the quadratic is what the constraints are signalling — Reading the constraints backwards to infer the intended complexity works here as elsewhere: n ≤ 300 rules out any need for cleverness beyond the per-anchor slope map.
Recall card
Section titled “Recall card”- Cue — the problem is arithmetic or geometric rather than structural: digits, powers, primes, points, rotations, spirals.
- Never compare slopes as floats. Reduce
(dx, dy)bygcd, then canonicalise the sign. Exact, and vertical lines need no special case. - Carry problems walk from the right, return early when a digit is below 9, and need the
[1] + digitsline for the all-nines case. - Powers — exponentiation by squaring, : square the base every round, multiply into the result on set bits. Handle a negative exponent once, up front.
- Primes — a sieve for many queries (, inner loop starts at
i², outer stops at ); trial division to for one. - Matrix in place — rotate = transpose then reverse each row; spiral = four shrinking boundaries.
- Big values — keep digits in an array; the point of these problems is that the number does not fit.
- Expect in geometry and read the constraints before trying to beat it.
- Digit arithmetic: process from the least significant end, return early when no carry is needed, and handle the case where the answer is longer than the input.
- Exponentiation by squaring uses the exponent’s bits for instead of
. Negative exponents take the reciprocal, and
-2^31is the overflow trap in fixed-width languages. - Never compare slopes as floats. Normalise
(dx, dy)by theirgcdand canonicalise the sign.gcd(0, k) == abs(k)makes vertical and horizontal lines work automatically. - For collinearity, the cross product is the exact, division-free primitive.
- When a problem seems to need a huge intermediate value (a factorial, a giant power), look for a counting argument instead — LC 172 is four lines once you see it.
- Read the constraints: mentions of are telling you about overflow and about the intended solution.
Next: Number Theory for Competitive Programming — sieves, modular inverses, and the identities worth memorising.
pch.coffeeTagline
pch.coffeeCtapch.feedbackHeading
pch.feedbackSubheading