🌍
Why does this topic matter?
Math is the hidden engine inside the most elegant algorithm
solutions. Problems that look like they need O(N) brute-force often
have O(log N) math-based shortcuts. Can you check if a number is
prime in 17 steps instead of 1 million? Can you compute 21000
without overflow? Can you find GCD without a loop of a million
iterations? The answer to all three is yes — and those answers
appear directly in FAANG interviews at Google, Amazon, Microsoft,
and Apple. You don't need a math degree. You need 6 patterns.
📖 Before We Start — The Big Picture
Mathematics gives algorithms their efficiency. Consider finding if a
number N is prime. The naïve approach checks all divisors from 2 to N
— that's O(N) steps. The mathematical observation that "factors come
in pairs, and one is always ≤ √N" reduces it to O(√N). For N =
1,000,000 that's the difference between 1,000,000 checks and 1,000
checks.
Modular arithmetic is clock arithmetic — on a 12-hour
clock, 11 + 3 = 2, not 14. This is how computers handle numbers larger
than the maximum int value: instead of overflowing, we keep taking the
remainder. The pattern "output mod 109+7" appears in almost
every combinatorics or large-number problem on LeetCode.
🔗
How it connects: Lecture 6 (Bit Manipulation)
introduced fast exponentiation using bit shifts — this lecture
formalizes it as Binary Exponentiation. Lecture 8 (Arrays & Strings)
frequently uses digit extraction (n % 10,
n / 10) and mathematical observations to solve
string/number problems efficiently.
🔢 Why Math Matters in FAANG Interviews
💡
You don't need a math degree. You need ~6
patterns:
Prime check · Sieve · GCD/LCM · Modular
arithmetic · Fast exponentiation · Combinatorics
These appear at Google, Amazon, Apple, Microsoft — often as
the key trick that turns O(n) into O(log n).
| Pattern |
Example Problem |
Naive |
With Math |
| Prime check |
Is n prime? |
O(n) |
O(√n) |
| All primes up to N |
Count Primes (LC 204) |
O(n√n) |
O(n log log n) |
| GCD |
Largest common divisor |
O(min(a,b)) |
O(log min(a,b)) |
| Modular power |
x^n mod m |
O(n) |
O(log n) |
| Combinations |
Count paths in grid |
Exponential |
O(n) |
🔵 Prime Numbers
A number n is prime if it is > 1
and divisible only by 1 and itself. Every integer > 1 is either
prime or a unique product of primes (Fundamental Theorem of
Arithmetic).
Trial Division — O(√n)
🔑
Key Insight: If n has a factor > √n, it
must also have one < √n (factors come in pairs). So only
check divisors up to √n.
boolean isPrime(int n) {
if (n < 2) return false;
if (n == 2) return true;
if (n % 2 == 0) return false;
for (int i = 3; i * i <= n; i += 2) // only odd divisors
if (n % i == 0) return false;
return true;
}
// isPrime(25): i=3(9≤25,25%3=1) i=5(25≤25,25%5=0) → false ✓ (25=5×5)
Sieve of Eratosthenes — O(n log log n)
Find all primes up to N. Mark each prime's multiples
as composite. Start marking at i² (smaller multiples already
marked by earlier primes).
boolean[] sieve(int n) {
boolean[] comp = new boolean[n+1]; // false=prime
comp[0] = comp[1] = true;
for (int i = 2; (long)i*i <= n; i++)
if (!comp[i])
for (int j = i*i; j <= n; j += i) // start at i²
comp[j] = true;
return comp; // comp[i]=false → prime
}
Sieve(10): step by step Start: 2 3 4 5 6 7 8 9 10 i=2: mark
4,6,8,10 → left: 2 3 5 7 9 i=3: mark
9 → left: 2 3 5 7 i=4: 4×4=16 >
10, stop. Primes ≤10: 2, 3, 5, 7 ✓
⚠
Memory: Sieve uses O(n) space. For n=107
that's ~10MB. Use a segmented sieve for n >
108.
⚙ GCD & LCM — Euclidean Algorithm
📐
Core Formula:
gcd(a, b) = gcd(b, a % b) Base case:
gcd(a, 0) = a
LCM:
lcm(a, b) = (a / gcd(a,b)) * b ← divide
first to prevent overflow
int gcd(int a, int b) { return b == 0 ? a : gcd(b, a%b); }
int gcdIter(int a, int b) { // iterative (avoids stack overflow)
while (b != 0) { int t = b; b = a%b; a = t; }
return a;
}
long lcm(long a, long b) { return (a / gcd((int)a, (int)b)) * b; }
// gcd(48,18): →gcd(18,12) →gcd(12,6) →gcd(6,0) = 6 ✓
// lcm(4,6) = (4/2)*6 = 12 ✓
Time O(log min(a,b))
Space O(1)
🔄 Modular Arithmetic
When results exceed long range (e.g. 21000),
use modular arithmetic. Most FAANG problems that say "output mod
109+7" require this.
📐
Rules (mod m):
(a+b) % m = ((a%m)+(b%m)) % m
(a×b) % m = ((a%m)×(b%m)) % m
(a−b) % m = ((a%m)−(b%m)+m) % m ←
+m prevents negatives!
Division: use modular inverse (Fermat's little theorem when m is
prime)
Binary Exponentiation (Fast Power) — O(log n)
xn = (xn/2)2 if n is even
| x × xn-1 if n is odd. This halves the
exponent each step.
long fastPow(long base, long exp, long mod) {
long result = 1;
base %= mod;
while (exp > 0) {
if ((exp & 1) == 1) result = result * base % mod; // odd exp: accumulate
base = base * base % mod; // square the base
exp >>= 1; // exp = exp/2
}
return result;
}
// fastPow(2,10,1000):
// exp=1010&sub2;: base=2,4,16,256
// Bits set at positions 1,3 → result = 4×256=1024 → 1024%1000=24 ✓
🧮 Prime Factorization — O(√n)
Map<Integer,Integer> primeFactors(int n) {
Map<Integer,Integer> f = new HashMap<>();
for (int i = 2; (long)i*i <= n; i++)
while (n % i == 0) { f.merge(i,1,Integer::sum); n /= i; }
if (n > 1) f.put(n, 1);
return f;
}
// 360 = 2³×3²×5 → {{2:3, 3:2, 5:1}}
// Divisor count = (3+1)(2+1)(1+1) = 24 divisors
🎲 Combinatorics — Counting Without Listing
📐
C(n,r) = n! / (r! × (n-r)!) (n choose
r)
Pascal's Rule: C(n,r) = C(n-1,r-1) + C(n-1,r)
Tip: C(n,r) = C(n,n-r) ← always choose
the smaller side to minimize computation
long[][] pascalTriangle(int n) {
long[][] C = new long[n+1][n+1];
for (int i = 0; i <= n; i++) {
C[i][0] = 1;
for (int j = 1; j <= i; j++)
C[i][j] = C[i-1][j-1] + C[i-1][j]; // Pascal's Rule
}
return C;
}
// C[4][2] = C(4,2) = 6 ways to choose 2 from 4 ✓
Pascal's Triangle: Row 0: 1 Row 1:
1 1 Row 2: 1 2 1 Row
3: 1 3 3 1 Row 4:
1 4 6 4 1 ← C(4,2)=6 (paths in 4-step grid)
Each number = sum of the two directly above (Pascal's Rule)
💪 In-Lecture Practice Problems
Work through in order. Click a card to expand. Try solving before
revealing the solution.
Problem
Count the number of prime numbers
strictly less than n.
Input: n=10 → Output: 4 (primes: 2, 3, 5, 7) Input:
n=0 → Output: 0 Input: n=1 → Output: 0
Think First — Before looking at solution
A brute-force checks each number up to n with isPrime() —
that's O(n√n). Can you do better?
Key insight: if p is prime, then 2p, 3p, 4p... are
all composite. Mark them off in bulk (Sieve).
Why start inner loop at p²? Because all smaller multiples
of p (e.g. 2p, 3p) were already marked by earlier primes.
▶ Solution + Dry Run
int countPrimes(int n) {
if (n <= 2) return 0;
boolean[] comp = new boolean[n]; // comp[i]=true means composite
comp[0] = comp[1] = true;
for (int i = 2; (long)i*i < n; i++)
if (!comp[i])
for (int j = i*i; j < n; j += i) comp[j] = true;
int cnt = 0;
for (int i = 2; i < n; i++) if (!comp[i]) cnt++;
return cnt;
}
// n=10: mark 4,6,8,9,10 → primes left: 2,3,5,7 → count=4 ✓
TimeO(n log log n)
SpaceO(n)
Problem
Given an integer array nums, return the
GCD of the smallest and largest element.
Input: [2,5,6,9,10] → Output: 2 (gcd(2,10)=2) Input:
[7,5,6,8,3] → Output: 1 (gcd(3,8)=1) Input: [3,3]
→ Output: 3
Think First — Before looking at solution
You only need gcd(min, max) — not gcd of all elements.
Use the Euclidean algorithm: gcd(a, b) = gcd(b, a%b). Base case:
gcd(a, 0) = a.
▶ Solution + Dry Run
int findGCD(int[] nums) {
int min = nums[0], max = nums[0];
for (int x : nums) { min = Math.min(min,x); max = Math.max(max,x); }
return gcd(min, max);
}
int gcd(int a, int b) { return b == 0 ? a : gcd(b, a%b); }
// [2,5,6,9,10]: min=2,max=10 → gcd(2,10)=gcd(10,2%10?)
// gcd(2,10): gcd(10,2) → gcd(2,0) = 2 ✓
TimeO(n + log max)
SpaceO(1)
Problem
Implement pow(x, n) — x raised to power n. n
can be negative.
Input: x=2.0, n=10 → Output: 1024.0 Input: x=2.0, n=-2
→ Output: 0.25 Input: x=2.1, n=3 → Output: 9.261
Think First — Before looking at solution
Naive: multiply x by itself n times — O(n). Too slow for
large n.
Key insight: x^n = (x²)^(n/2) if n is even. This halves the
exponent each step.
Edge cases: negative n (flip x to 1/x), n = Integer.MIN_VALUE
(use long to avoid overflow).
▶ Solution + Dry Run
double myPow(double x, int n) {
long N = n; // use long: n=-2^31 would overflow on negation
if (N < 0) { x = 1.0 / x; N = -N; }
double res = 1;
while (N > 0) {
if ((N & 1) == 1) res *= x; // odd exponent: take one x
x *= x; // square the base
N >>= 1; // halve the exponent
}
return res;
}
// myPow(2,10): N=1010&sub2;
// N=10(even): x=4, N=5
// N=5(odd): res=4, x=16, N=2
// N=2(even): x=256, N=1
// N=1(odd): res=4×256=1024 → 1024.0 ✓
Problem
Repeatedly replace n with the
sum of squares of its digits. Is n "happy"
(eventually reaches 1)?
Input: 19 → true (1²+9²=82 →68 →100
→1 ✓) Input: 2 → false (enters infinite cycle:
4→16→37→58→89→145→42→20→4...)
Think First — Before looking at solution
How do you know when it's NOT happy? It cycles without reaching
1.
Two approaches: (1) Use a HashSet to detect if a number
repeats.
(2) Use Floyd's cycle detection (fast + slow pointer) —
same technique as Linked List cycle!
▶ Solution + Dry Run
boolean isHappy(int n) {
Set<Integer> seen = new HashSet<>();
while (n != 1 && !seen.contains(n)) {
seen.add(n);
n = sumOfSquares(n);
}
return n == 1;
}
int sumOfSquares(int n) {
int s = 0;
while (n > 0) { int d = n%10; s += d*d; n /= 10; }
return s;
}
// isHappy(19): 82→68→100→1 → true ✓
TimeO(log n)
SpaceO(log n)
Problem
Count unique paths from top-left to bottom-right of m×n
grid. Only right or down moves allowed.
Input: m=3, n=7 → Output: 28 Input: m=3, n=2 → Output:
3 Input: m=1, n=1 → Output: 1
Think First — Before looking at solution
Total moves needed: (m-1) down + (n-1) right = m+n-2 total
moves.
You just need to CHOOSE which (m-1) of those moves are "down"
— the rest are "right".
Answer = C(m+n-2, m-1). This is O(min(m,n)) vs O(m×n) for
DP.
▶ Solution + Dry Run
int uniquePaths(int m, int n) {
long result = 1;
int r = m-1, total = m+n-2;
for (int i = 0; i < r; i++)
result = result * (total - i) / (i + 1); // compute C(total,r) incrementally
return (int) result;
}
// m=3,n=7: C(8,2) = 8*7/2 = 28 ✓
// m=3,n=2: C(3,2) = 3 ✓
TimeO(min(m,n))
SpaceO(1)
Problem
Return the rowIndex-th (0-indexed) row of Pascal's
Triangle using only O(rowIndex) space.
Input: rowIndex=3 → [1, 3, 3, 1] Input: rowIndex=0 →
[1] Input: rowIndex=1 → [1, 1]
Think First — Before looking at solution
Pascal's Rule: row[j] = row[j-1] + row[j] (from previous
row).
Key trick for O(n) space: update right-to-left so you don't
overwrite values you still need.
Why right-to-left? Because row[j] needs old row[j] and old
row[j-1]. Updating left-to-right would corrupt row[j-1].
▶ Solution + Dry Run
List<Integer> getRow(int n) {
List<Integer> row = new ArrayList<>(); row.add(1);
for (int i = 1; i <= n; i++) {
for (int j = i-1; j >= 1; j--) // RIGHT TO LEFT
row.set(j, row.get(j-1) + row.get(j));
row.add(1);
}
return row;
}
// n=3: [1] → [1,1] → [1,2,1] → [1,3,3,1] ✓
Problem
Convert column number to Excel title. A=1, Z=26, AA=27, AZ=52,
ZY=701.
Input: 1 → "A" Input: 28 → "AB" Input: 701 → "ZY"
Think First — Before looking at solution
This is base-26 BUT with no zero digit (A=1, not A=0). That's
the trick.
Before taking (n % 26) to get the current letter, you must
subtract 1 from n to shift to 0-indexed.
Example: 26 should give "Z", not "Z" then "A". After n-- we get
n%26=25='Z', n/26=0. Done.
▶ Solution + Dry Run
String convertToTitle(int n) {
StringBuilder sb = new StringBuilder();
while (n > 0) {
n--; // shift to 0-indexed
sb.insert(0, (char)('A' + n % 26)); // get current letter
n /= 26;
}
return sb.toString();
}
// n=28: n=27, 27%26=1='B', n=1; n=0, 0%26=0='A', n=0 → "AB" ✓
// n=701: n=700, 700%26=24='Y', n=26; n=25, 25%26=25='Z', n=0 → "ZY" ✓
TimeO(log n)
SpaceO(log n)
Problem
An ugly number has only prime factors 2, 3, and
5. Find the n-th ugly number.
Ugly sequence: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15... Input: n=10
→ Output: 12 Input: n=1 → Output: 1
Think First — Before looking at solution
Every ugly number is generated by multiplying a previous ugly
number by 2, 3 or 5.
Use 3 pointers (i2, i3, i5) tracking the "next" ugly number to
multiply for each factor.
Always pick the minimum of ugly[i2]×2, ugly[i3]×3,
ugly[i5]×5 as the next ugly number.
▶ Solution + Dry Run
int nthUglyNumber(int n) {
int[] ugly = new int[n]; ugly[0] = 1;
int i2=0, i3=0, i5=0;
for (int i = 1; i < n; i++) {
int next2 = ugly[i2]*2, next3 = ugly[i3]*3, next5 = ugly[i5]*5;
int next = Math.min(next2, Math.min(next3, next5));
ugly[i] = next;
if (next == next2) i2++; // advance whichever pointer(s) produced minimum
if (next == next3) i3++;
if (next == next5) i5++;
}
return ugly[n-1];
}
// Sequence: 1,2,3,4,5,6,8,9,10,12 → ugly[9]=12 ✓
📝 Assignment
📋
Lecture 7 Assignment — 30 Problems
Primes, modular arithmetic, GCD patterns, combinatorics, number
theory tricks. Complete before starting Lecture 8.
✅ Lecture Completion Checklist
Don't proceed to Lecture 8 until all are checked.
✓
I can check if n is prime in O(√n) without notes
✓
I can implement the Sieve of Eratosthenes from memory and explain
why inner loop starts at i²
✓
I can implement gcd(a,b) recursively and iteratively and derive
lcm from it
✓
I know all 4 modular arithmetic rules — especially (a-b)%m
needs +m to prevent negatives
✓
I can implement fastPow(base, exp, mod) using binary
exponentiation from memory
✓
I understand Pascal's Rule C(n,r)=C(n-1,r-1)+C(n-1,r) and can
build the triangle in O(n²)
✓
I solved Count Primes (
LC 204) with Sieve — not brute force
✓
I solved Unique Paths (
LC 62) using C(m+n-2, m-1) math — not only DP
✓
I can state: isPrime O(√n), Sieve O(n log log n), GCD O(log
n), fastPow O(log n)
✓
I completed at least 12 problems from the Assignment
🚀
You're ready for Lecture 8: Arrays & Strings!
Phase 2 begins — The most common interview category (40%+ of
FAANG questions).