Part 2 · Lesson 13 · 26 min read

Basic Maths for DSA

Digits, divisors, primes, the sieve, GCD and LCM, and fast powers.

On this page

  1. A little maths, a lot less work
  2. Digit problems revisited
  3. All divisors in √n steps
  4. Checking for a prime
  5. The Sieve of Eratosthenes
  6. GCD with Euclid's algorithm
  7. Traced: gcd(252, 105)
  8. LCM from the GCD
  9. Remainders and modular arithmetic
  10. Fast exponentiation
  11. The costs side by side
  12. Practice questions (9)
  13. Make it stick
  14. What's next

A little maths, a lot less work

You do not need advanced maths for coding interviews. You need about six small facts. Each one turns a slow loop into a fast loop. Lesson 12 gave you the words to measure this. Checking every number up to n is O(n) (the work grows in step with n). With the right fact, the same job becomes O(√n) or O(log n), which is much less work.

You met some of these ideas briefly in Lessons 4, 5 and 7: the prime check, the GCD loop, the sieve and fast power. This lesson explains why they work and what they cost. Then you can rebuild them from understanding, not from memory.

Digit problems revisited

Lesson 5's digit loop takes a number apart. The remainder operator % gives what is left after a division, so n % 10 is the last digit (for example, 4072 % 10 is 2). Math.floor(n / 10) divides by 10 and drops the decimal part, which removes that last digit. Two new observations:

  • It is O(log n). Each pass removes one digit. A number n has about log₁₀ n digits (log₁₀ n means: how many times you can divide n by 10). Even n = 1018 needs only 19 passes.
  • 10 is not special. Replace 10 with 2 and the same loop gives the binary digits. Binary is the way computers write numbers, using only 0 and 1. A single 0 or 1 is called a bit. You will read bits this way in Lesson 58.
JavaScript
function digitsOf(n, base = 10) {  if (n === 0) return [0];  const digits = [];  while (n > 0) {    digits.push(n % base);          // last digit    n = Math.floor(n / base);       // remove it  }  return digits.reverse();          // they came out last-first}console.log(digitsOf(4072));        // [ 4, 0, 7, 2 ]console.log(digitsOf(13, 2));       // [ 1, 1, 0, 1 ]   13 in binary

All divisors in √n steps

A divisor of n is a whole number that divides n with remainder 0. For example, the divisors of 6 are 1, 2, 3 and 6. The obvious way to find them all is to try every number from 1 to n. That is O(n). But divisors come in pairs: if i divides n, so does n / i.

Dry run · divisor pairs of 36
i (small)36 / i (large)i × (36 / i)
13636
21836
31236
4936
6636 ← i = √36, the pairs meet

In every pair, the smaller number is at most √n. So we only need to try i up to √n and collect both halves of each pair.

JavaScript
function divisors(n) {  const small = [];  const large = [];  for (let i = 1; i * i <= n; i++) {    if (n % i === 0) {      small.push(i);                     // the small half of the pair      if (i !== n / i) large.push(n / i);  // its partner (skip 6 × 6 twice)    }  }  return [...small, ...large.reverse()];}console.log(divisors(36)); // [ 1, 2, 3, 4, 6, 9, 12, 18, 36 ]console.log(divisors(13)); // [ 1, 13 ]

The √n (square root of n) is the number that gives n when you multiply it by itself. For example, √36 = 6. The loop condition i * i <= n means “i ≤ √n”. It avoids Math.sqrt and its rounding. For n = 1012, that is 106 passes instead of 1012. This is the difference between an instant answer and several hours.

Checking for a prime

A prime number is a whole number greater than 1 that has no divisors except 1 and itself. For example, 7 is prime, and 8 is not (2 divides it). The pairs idea gives a fast check. If n has any divisor other than 1 and n, the smaller number of its pair is at most √n. So if nothing up to √n divides n, then nothing does.

JavaScript
function isPrime(n) {  if (n < 2) return false;                 // 0 and 1 are not prime  for (let i = 2; i * i <= n; i++) {    if (n % i === 0) return false;         // found a divisor  }  return true;}console.log(isPrime(97));  // trueconsole.log(isPrime(91));  // false   (7 × 13)console.log(isPrime(1));   // false

Time O(√n), space O(1) (it needs no extra memory). Remember the edge cases: 0 and 1 are not prime, and 2 is the only even prime.

The Sieve of Eratosthenes

The Sieve of Eratosthenes is an algorithm that finds all prime numbers up to n. To find every prime up to n, checking each number separately costs about n × √n. The sieve does better by working the other way round. It does not ask “is this number prime?”. Instead, it crosses out every number that is not prime, like a sieve (a kitchen strainer) that lets only the primes through. A multiple of i is any number you get by multiplying i by a whole number: 3, 6, 9, 12 are multiples of 3.

  1. Start with every number from 2 to n marked as prime.
  2. Take the smallest number still marked. It is prime. Cross out all its multiples.
  3. Repeat. Stop once i × i is greater than n — every number left is prime.
Dry run · primesUpTo(30)
iStill marked?Crosses out (from i × i)
2yes4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30
3yes9, 12, 15, 18, 21, 24, 27, 30
4no — skip
5yes25, 30
66 × 6 = 36 > 30 — stop

Left unmarked: 2, 3, 5, 7, 11, 13, 17, 19, 23, 29.

Why start crossing at i * i? Smaller multiples of i, like 2 × 5 and 3 × 5 (when i is 5), were already crossed out by 2 and 3. And why stop at √n? Any non-prime number up to n has a divisor that is at most √n, so it has already been crossed out.

JavaScript
function primesUpTo(n) {  const isPrime = new Array(n + 1).fill(true);  isPrime[0] = false;  isPrime[1] = false;  for (let i = 2; i * i <= n; i++) {    if (!isPrime[i]) continue;               // already crossed out    for (let j = i * i; j <= n; j += i) {      isPrime[j] = false;                    // cross out multiples of i    }  }  const primes = [];  for (let i = 2; i <= n; i++) if (isPrime[i]) primes.push(i);  return primes;}console.log(primesUpTo(30)); // [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 ]

The sieve runs in about O(n log log n) time. This is so close to O(n) that you can treat it as linear. It uses O(n) space for the array. It is the standard answer whenever a problem needs many primes, for example “count the primes below 5,000,000”.

GCD with Euclid's algorithm

The GCD (greatest common divisor) of two numbers is the largest number that divides both: gcd(12, 18) = 6. You can try every candidate from the smaller number downwards. That works, but it is O(min(a, b)). Euclid (a Greek mathematician) found a much faster way more than 2,000 years ago. Euclid's algorithm finds the GCD by replacing the pair (a, b) with the smaller pair (b, a % b) again and again:

Euclid's rule

gcd(a, b) = gcd(b, a % b), and gcd(a, 0) = a.

Why: any number that divides both a and b also divides a − b, a − 2b, and so on. In the end it divides the remainder a % b. So the pair (b, a % b) has exactly the same common divisors as (a, b), but the numbers are smaller.

Traced: gcd(252, 105)

step 1 / 16
Set up
gcd(252, 105)

We want the largest number that divides both.

▶function gcd(a, b) {2  while (b !== 0) {3    const r = a % b;4    a = b;5    b = r;6  }7  return a;8}9console.log(gcd(252, 105));
Variables
a252
b105
Console
(nothing printed yet)
Each pass replaces (a, b) with (b, a % b). The numbers shrink fast: the remainder is always smaller than b. · interactive

The remainder becomes at most half as big every two steps. So Euclid's algorithm is O(log min(a, b)). For numbers around a billion, that is a few dozen passes at most.

LCM from the GCD

The LCM (least common multiple) is the smallest positive number that both a and b divide: lcm(4, 6) = 12. It comes straight from the GCD, because gcd(a, b) × lcm(a, b) = a × b. So lcm(a, b) = a × b / gcd(a, b).

JavaScript
function gcd(a, b) {  while (b !== 0) [a, b] = [b, a % b];  return a;}function lcm(a, b) {  return (a / gcd(a, b)) * b;        // divide first so the number stays small}console.log(gcd(12, 18));  // 6console.log(lcm(4, 6));    // 12console.log(lcm(21, 6));   // 42

For more than two numbers, apply the function to one pair at a time: gcd(a, b, c) = gcd(gcd(a, b), c), and the same for the LCM. In JavaScript you can do this with one reduce (an array method that folds a list into one value).

Remainders and modular arithmetic

Many problems count something enormous, such as the number of paths in a grid or the number of arrangements of a string. They then say “return the answer modulo 109 + 7”. The remainder is what is left after a division: 17 divided by 5 is 3 with remainder 2. Modulo means “give me that remainder”. So the problem wants only the remainder after dividing by 1,000,000,007. Working only with remainders is called modular arithmetic. It follows these rules:

TEXT
const M = 1_000_000_007;// Addition and multiplication: take % as often as you like(a + b) % M  ===  ((a % M) + (b % M)) % M(a * b) % M  ===  ((a % M) * (b % M)) % M// Subtraction: add M before the final % so the result is never negative(a - b) % M  →  ((a % M) - (b % M) + M) % M

So you can take % M after every addition or multiplication, and the numbers never grow large. Watch two details that are specific to JavaScript:

1. Negative numbers. In JavaScript, the % operator gives a result with the same sign as the left number. So a negative number gives a negative remainder:

JavaScript
console.log(7 % 3);              // 1console.log(-7 % 3);             // -1   JavaScript keeps the sign of the left sideconsole.log(((-7 % 3) + 3) % 3); // 2    the mathematical remainder

2. Multiplying two large remainders. A normal JavaScript number is stored as a 64-bit decimal (floating-point) value. It is exact for whole numbers only up to 253 ≈ 9 × 1015. Two remainders near 109 multiply to about 1018, which is past that limit. The result silently loses its last digits. Use BigInt for those multiplications. BigInt is a JavaScript type for whole numbers of any size, and you write one with an n at the end, like 12n:

JavaScript
const M = 1_000_000_007;// Wrong: the product is about 1.2 × 10^17, beyond the safe limit of 2^53 ≈ 9 × 10^15console.log((123456789 * 987654321) % M);                         // 259106854// Right: BigInt keeps every digitconsole.log(Number((123456789n * 987654321n) % BigInt(M)));       // 259106859
Silent and wrong

The first answer is off by 5, and no error is shown. Addition is safe (two values below 109 add to about 2 × 109). But any multiplication under a large modulus in JavaScript should use BigInt.

Fast exponentiation

xn means x multiplied by itself n times (the exponent n says how many). Doing that with n multiplications is O(n). Fast exponentiation (also called exponentiation by squaring) is a method that finds xn with only about log n multiplications. It uses the fact that squaring doubles the exponent: x → x² → x⁴ → x⁸. Any n can be written as a sum of these powers of two. Those are its binary digits. For 13 = 8 + 4 + 1 (binary 1101):

3¹³ = 3⁸ × 3⁴ × 3¹

So walk through the bits of n with the digit loop (base 2). Keep squaring x. Whenever the current bit is 1, multiply the current x into the result.

JavaScript
function power(x, n) {  let result = 1;  while (n > 0) {    if (n % 2 === 1) result *= x;   // this bit of n is 1: include the current x    x *= x;                         // x, x², x⁴, x⁸ …    n = Math.floor(n / 2);          // move to the next bit  }  return result;}console.log(power(3, 13));  // 1594323console.log(power(2, 10));  // 1024
Dry run · power(3, 13)
nn odd?resultx after squaring
13yes → result × 339
6no381
3yes → result × 812436,561
1yes → result × 6,5611,594,32343,046,721

Four passes instead of thirteen multiplications. For n = 10⁹ it is about 30 passes instead of a billion: O(log n).

In real problems, fast power is almost always used together with a modulus, for example (21,000,000,000) % (109 + 7). Practice question 7 builds that version, with BigInt.

The costs side by side

TaskObvious wayWith the right factThe fact
Digits of n—O(log n)One digit removed per pass
All divisors of nO(n)O(√n)Divisors come in pairs
Is n prime?O(n)O(√n)A divisor pair has one side ≤ √n
All primes up to nO(n√n)O(n log log n)Cross out multiples (sieve)
gcd(a, b)O(min(a, b))O(log min(a, b))gcd(a, b) = gcd(b, a % b)
xnO(n)O(log n)Squaring doubles the exponent

Practice questions

Before each solution, read the constraints (the limits on the input size) and use the table from Lesson 12 to decide which growth rate is fast enough. Most of these questions are about choosing the right fact.

Q1GCD of the smallest and largestEasy

Return the greatest common divisor of the smallest and the largest number in nums.

Example 1
Input: [2, 5, 6, 9, 10]
Output: 2
Explanation: Smallest 2, largest 10, gcd(2, 10) = 2.
Example 2
Input: [7, 5, 6, 8, 3]
Output: 1
Explanation: Smallest 3, largest 8. They share no divisor except 1.
Example 3
Input: [3, 3]
Output: 3
Explanation: gcd(3, 3) = 3.
Stuck? Open one hint at a time
Hint 1
Two separate jobs: find the minimum and maximum (one pass), then one GCD.
Hint 2
Use Euclid: gcd(a, b) = gcd(b, a % b).
Show all 2 solutions with explanations
Approach 1Try every candidate
The idea

Count down from the smallest value. The first number that divides both values is the GCD.

JavaScript
function findGCD(nums) {  const lo = Math.min(...nums);  const hi = Math.max(...nums);  for (let d = lo; d >= 1; d--) {    if (lo % d === 0 && hi % d === 0) return d;  }}console.log(findGCD([2, 5, 6, 9, 10])); // 2console.log(findGCD([7, 5, 6, 8, 3]));  // 1
How it works

This is correct, but the GCD part is O(min), meaning the work grows with the smaller value. With values up to 1,000 (the LeetCode limit) that is fine. With values up to 109 it is too slow.

Approach 2Euclid
The idea

Find the minimum and maximum, then apply Euclid's rule.

JavaScript
function gcd(a, b) {  while (b !== 0) [a, b] = [b, a % b];  return a;}function findGCD(nums) {  return gcd(Math.min(...nums), Math.max(...nums));}console.log(findGCD([2, 5, 6, 9, 10])); // 2console.log(findGCD([7, 5, 6, 8, 3]));  // 1console.log(findGCD([3, 3]));           // 3
How it works

Finding the min and max is O(n), and the GCD is O(log). The line [a, b] = [b, a % b] is called destructuring assignment. It sets both variables at once. It does the same job as the three lines in the traced version, but shorter.

Which approach should you use?

Use Euclid. It is just as short, and its speed hardly changes when the values are large. (LeetCode 1979.)

Q2Smallest number divisible by 1 to nEasy

Return the smallest positive number that every number from 1 to n divides evenly (1 ≤ n ≤ 20). “Divides evenly” means the remainder is 0.

Example 1
Input: 4
Output: 12
Explanation: 12 is divisible by 1, 2, 3 and 4, and no smaller number is.
Example 2
Input: 10
Output: 2520
Explanation: The classic answer: lcm(1, 2, …, 10).
Example 3
Input: 20
Output: 232792560
Explanation: Still well inside the safe range of JavaScript numbers (below 2^53).
Stuck? Open one hint at a time
Hint 1
“Divisible by all of them” is exactly what the LCM (least common multiple) means.
Hint 2
lcm(1 … n) = lcm(lcm(1 … n−1), n). Build the answer one number at a time.
Show all 2 solutions with explanations
Approach 1Try multiples
The idea

Test n, 2n, 3n, … until one is divisible by every number from 1 to n.

JavaScript
function smallestMultiple(n) {  for (let x = n; ; x += n) {    let ok = true;    for (let d = 1; d <= n; d++) {      if (x % d !== 0) { ok = false; break; }    }    if (ok) return x;  }}console.log(smallestMultiple(4));  // 12console.log(smallestMultiple(10)); // 2520
How it works

This works for small n. For n = 20 it tests over 11 million candidates. The answer grows very fast, so this soon becomes too slow.

Approach 2Running LCM
The idea

Start with 1 and add in each number one at a time: result = lcm(result, k).

JavaScript
function gcd(a, b) {  while (b !== 0) [a, b] = [b, a % b];  return a;}function smallestMultiple(n) {  let result = 1;  for (let k = 2; k <= n; k++) {    result = (result / gcd(result, k)) * k;  }  return result;}console.log(smallestMultiple(4));  // 12console.log(smallestMultiple(10)); // 2520console.log(smallestMultiple(20)); // 232792560
How it works
Dry run · smallestMultiple(6)
kgcd(result, k)result after
212
316
4212
5160
6660

When k already divides the result (k = 6), the GCD equals k and the result does not change.

Which approach should you use?

Approach 2 makes n GCD calls, so it is about O(n log n) in total. Dividing by the GCD before multiplying keeps the in-between values small.

Q3Exactly three divisorsEasy

Return true if n has exactly three positive divisors.

Example 1
Input: 2
Output: false
Explanation: Divisors: 1, 2. Only two.
Example 2
Input: 4
Output: true
Explanation: Divisors: 1, 2, 4.
Example 3
Input: 9
Output: true
Explanation: Divisors: 1, 3, 9.
Example 4
Input: 12
Output: false
Explanation: Divisors: 1, 2, 3, 4, 6, 12.
Stuck? Open one hint at a time
Hint 1
Count divisors using pairs, up to √n.
Hint 2
Divisors come in pairs. To get an odd count, one pair must be a single number, so i × i = n.
Hint 3
So n must be a perfect square (a number like 4, 9 or 16 that is some whole number times itself), n = p². Its only other divisor must be p. What does that say about p?
Show all 2 solutions with explanations
Approach 1Count divisors in pairs
The idea

Loop i up to √n. Each divisor i adds 2 to the count (i and n / i), or adds 1 if they are the same number.

JavaScript
function isThree(n) {  let count = 0;  for (let i = 1; i * i <= n; i++) {    if (n % i === 0) count += i * i === n ? 1 : 2;  }  return count === 3;}console.log(isThree(2));  // falseconsole.log(isThree(4));  // trueconsole.log(isThree(12)); // false
How it works

O(√n) time, O(1) space. This uses the pairs idea from the lesson directly.

Approach 2Square of a prime
The idea

Three divisors means the divisors are 1, p and p², where p is prime. So check that n is a perfect square and that its square root is prime.

JavaScript
function isPrime(n) {  if (n < 2) return false;  for (let i = 2; i * i <= n; i++) if (n % i === 0) return false;  return true;}function isThree(n) {  const r = Math.round(Math.sqrt(n));  return r * r === n && isPrime(r);}console.log(isThree(4));  // trueconsole.log(isThree(9));  // trueconsole.log(isThree(16)); // false   (1, 2, 4, 8, 16)
How it works

O(n1/4): the prime check runs only up to the square root of the square root. Math.sqrt returns a decimal (floating-point) number that can be slightly off. So we use Math.round and then check r * r === n to be safe.

Which approach should you use?

Approach 1 is the one to reach for first. Approach 2 shows the kind of insight interviewers like: think about the shape of the answer, not just the loop. (LeetCode 1952.)

Q4Happy numberEasy

Start with n. Replace it by the sum of the squares of its digits, and repeat. If this reaches 1, the number is happy. Return true if n is happy and false if it is not.

Example 1
Input: 19
Output: true
Explanation: 1² + 9² = 82 → 8² + 2² = 68 → 36 + 64 = 100 → 1 + 0 + 0 = 1.
Example 2
Input: 2
Output: false
Explanation: 2 → 4 → 16 → 37 → 58 → 89 → 145 → 42 → 20 → 4 → … The sequence repeats forever without reaching 1.
Stuck? Open one hint at a time
Hint 1
Write a helper function that returns the sum of the squares of the digits (use the digit loop).
Hint 2
The sequence either reaches 1 or repeats. How can you notice a repeat?
Hint 3
Keep every number you have seen in a Set (a JavaScript collection that stores each value only once and can check quickly whether a value is inside).
Show all 2 solutions with explanations
Approach 1Digit loop + Set of seen values
The idea

Keep replacing n with the sum of the squares of its digits. Stop at 1 (happy) or when a value repeats (not happy).

JavaScript
function digitSquareSum(n) {  let sum = 0;  while (n > 0) {    const d = n % 10;    sum += d * d;    n = Math.floor(n / 10);  }  return sum;}function isHappy(n) {  const seen = new Set();  while (n !== 1 && !seen.has(n)) {    seen.add(n);    n = digitSquareSum(n);  }  return n === 1;}console.log(isHappy(19)); // trueconsole.log(isHappy(2));  // false
How it works

Why must it repeat? Any number up to 109 has at most 10 digits, so the next value is at most 10 × 81 = 810. After the first step every value is small. The sequence can only visit a limited number of values, so one of them must come back.

Approach 2Two speeds, no Set
The idea

Move one value one step at a time (slow) and another value two steps at a time (fast). If the sequence goes in a loop, the fast one catches the slow one.

JavaScript
function digitSquareSum(n) {  let sum = 0;  for (; n > 0; n = Math.floor(n / 10)) sum += (n % 10) ** 2;  return sum;}function isHappy(n) {  let slow = n;  let fast = digitSquareSum(n);  while (fast !== 1 && slow !== fast) {    slow = digitSquareSum(slow);    fast = digitSquareSum(digitSquareSum(fast));  }  return fast === 1;}console.log(isHappy(19)); // trueconsole.log(isHappy(2));  // false
How it works

O(1) extra space. This “fast and slow pointer” idea comes back in Lesson 36, where it finds loops in linked lists.

Which approach should you use?

Use the Set. It is clear and easy to explain. Mention the fast and slow version if the interviewer asks you to avoid extra memory. (LeetCode 202.)

Q5Ugly numberEasy

A prime factor of n is a prime number that divides n. For example, the prime factors of 12 are 2 and 3. An ugly number is a positive integer whose only prime factors are 2, 3 and 5. Return whether n is ugly.

Example 1
Input: 6
Output: true
Explanation: 6 = 2 × 3.
Example 2
Input: 1
Output: true
Explanation: 1 has no prime factors at all, so it has none other than 2, 3 and 5.
Example 3
Input: 14
Output: false
Explanation: 14 = 2 × 7, and 7 is not allowed.
Example 4
Input: 0
Output: false
Explanation: Edge case: an ugly number must be positive.
Stuck? Open one hint at a time
Hint 1
Divide out every 2, then every 3, then every 5. (“Divide out” means keep dividing while the remainder is 0.)
Hint 2
What is left must be 1.
Show the solution with explanations
Approach 1Divide out the allowed factors
The idea

While n is divisible by 2, divide it by 2. Do the same for 3 and 5. If n is now 1, it had no other prime factors.

JavaScript
function isUgly(n) {  if (n <= 0) return false;  for (const p of [2, 3, 5]) {    while (n % p === 0) n /= p;  }  return n === 1;}console.log(isUgly(6));  // trueconsole.log(isUgly(1));  // trueconsole.log(isUgly(14)); // falseconsole.log(isUgly(0));  // false
How it works

Each division at least halves n, so this is O(log n). The n <= 0 check is needed. 0 % 2 is 0, so without the check the loop would divide 0 by 2 forever.

Which approach should you use?

The loop “divide out a factor while you can” is the core of prime factorisation. The next question builds the full version. (LeetCode 263.)

Q6Prime factorisationMedium

Prime factorisation means writing a number as a product of primes. Return the prime factors of n (n ≥ 2) in increasing order. Write a factor as many times as it divides n.

Example 1
Input: 360
Output: [2, 2, 2, 3, 3, 5]
Explanation: 360 = 2 × 2 × 2 × 3 × 3 × 5.
Example 2
Input: 97
Output: [97]
Explanation: 97 is prime.
Example 3
Input: 1000000007
Output: [1000000007]
Explanation: A large prime: the loop must stop at √n, not at n.
Stuck? Open one hint at a time
Hint 1
Try divisors from 2 upwards. Whenever d divides n, record it and divide it out. Repeat while d still divides n.
Hint 2
You only need d up to √n. If n is still greater than 1 after the loop, what must it be?
Show the solution with explanations
Approach 1Trial division up to √n
The idea
  1. For d = 2, 3, 4, … while d * d <= n: divide out d as many times as possible, recording it each time.
  2. If n is still greater than 1 at the end, what is left of n is itself a prime factor.
JavaScript
function primeFactors(n) {  const factors = [];  for (let d = 2; d * d <= n; d++) {    while (n % d === 0) {      factors.push(d);      n /= d;    }  }  if (n > 1) factors.push(n);   // what is left is prime  return factors;}console.log(primeFactors(360));        // [ 2, 2, 2, 3, 3, 5 ]console.log(primeFactors(97));         // [ 97 ]console.log(primeFactors(1000000007)); // [ 1000000007 ]
How it works
Dry run · primeFactors(360)
ddivides outn after
22, 2, 245
33, 35
4— (3 × 3 … 4 × 4 = 16 > 5, stop)5
after loopn = 5 > 1 → push 5

Non-prime d (like 4) never divides n, because its prime factors were already divided out.

Which approach should you use?

O(√n) time. Notice that the limit d * d <= n uses the n that keeps shrinking. So the loop often stops much earlier than the square root of the original number.

Q7Power modulo 10⁹ + 7Medium

Return the remainder of ab divided by 109 + 7 (written ab mod (109 + 7)), for 1 ≤ a ≤ 109 and 0 ≤ b ≤ 109.

Example 1
Input: a = 2, b = 10
Output: 1024
Explanation: 2¹⁰ = 1024, already smaller than the modulus.
Example 2
Input: a = 2, b = 100
Output: 976371285
Explanation: 2¹⁰⁰ has 31 digits; only its remainder is returned.
Example 3
Input: a = 3, b = 200
Output: 136318165
Explanation: Same idea.
Stuck? Open one hint at a time
Hint 1
b can be 109, so a loop of b multiplications is too slow. Use fast power (repeated squaring) from the lesson.
Hint 2
Take % M after every multiplication.
Hint 3
Remainders are up to about 109, so their product is about 1018. Which JavaScript type can handle that exactly?
Show all 2 solutions with explanations
Approach 1Fast power with Number (wrong)
The idea

Use the fast-power loop from the lesson and take the remainder after each multiplication.

JavaScript
const M = 1_000_000_007;function powMod(a, b) {  let result = 1;  a %= M;  while (b > 0) {    if (b % 2 === 1) result = (result * a) % M;    a = (a * a) % M;    b = Math.floor(b / 2);  }  return result;}console.log(powMod(2, 10));  // 1024        correct (small numbers)console.log(powMod(2, 100)); // 976371253   wrong — the right answer is 976371285
How it works

The algorithm is right, but the arithmetic is not. Once a is near 109, a * a goes past 253 and loses its last digits (precision). No error appears. You just get a wrong answer. This is a very common JavaScript bug in interviews.

Approach 2Fast power with BigInt
The idea

Use the same loop, but make every value a BigInt (a JavaScript type for whole numbers of any size), so products are exact.

JavaScript
const M = 1_000_000_007n;function powMod(a, b) {  let base = BigInt(a) % M;  let exp = BigInt(b);  let result = 1n;  while (exp > 0n) {    if (exp % 2n === 1n) result = (result * base) % M;    base = (base * base) % M;    exp = exp / 2n;              // BigInt division already rounds down  }  return Number(result);}console.log(powMod(2, 10));         // 1024console.log(powMod(2, 100));        // 976371285console.log(powMod(3, 200));        // 136318165console.log(powMod(2, 1000000000)); // 140625001
How it works

O(log b) time: about 30 passes for b = 109. You cannot mix BigInt values with normal numbers, so every constant gets an n: 2n, 1n, 0n. Convert back with Number() at the end. The result is below M, so this is safe.

Which approach should you use?

Always use the BigInt version when you multiply under a modulus of 109 + 7 in JavaScript. Test it with a large input. Small tests like 2¹⁰ pass even with the broken version.

Q8Pow(x, n)Medium

Compute xn without using ** or Math.pow. Here x is a decimal number and n is a whole number that may be negative.

Example 1
Input: x = 2, n = 10
Output: 1024
Explanation: 2¹⁰.
Example 2
Input: x = 2, n = -2
Output: 0.25
Explanation: A negative exponent means 1 / x²: 1 / 4.
Example 3
Input: x = 3, n = 13
Output: 1594323
Explanation: The example traced in the lesson.
Stuck? Open one hint at a time
Hint 1
n can be as large as 231 − 1 (about 2 billion), so an O(n) loop is too slow.
Hint 2
For a negative n: xn = (1 / x)−n.
Show all 2 solutions with explanations
Approach 1Multiply n times
The idea

Multiply x into the result |n| times (|n| means n without its minus sign). For a negative n, take 1 divided by the result.

JavaScript
function myPow(x, n) {  let result = 1;  for (let i = 0; i < Math.abs(n); i++) result *= x;  return n < 0 ? 1 / result : result;}console.log(myPow(2, 10)); // 1024console.log(myPow(2, -2)); // 0.25
How it works

O(n). With n around 2 × 109 this is billions of multiplications, which is far too slow for these limits.

Approach 2Fast exponentiation
The idea

Handle the sign first, then use repeated squaring (fast exponentiation).

JavaScript
function myPow(x, n) {  if (n < 0) {    x = 1 / x;    n = -n;  }  let result = 1;  while (n > 0) {    if (n % 2 === 1) result *= x;    x *= x;    n = Math.floor(n / 2);  }  return result;}console.log(myPow(2, 10)); // 1024console.log(myPow(2, -2)); // 0.25console.log(myPow(3, 13)); // 1594323
How it works

O(log n): about 31 passes for the largest n. There is no modulus here, because x is a decimal number and the answer is also a decimal.

Which approach should you use?

Approach 2 is the expected answer. Mention the negative exponent edge case before you write the loop. (LeetCode 50.)

Q9Closest prime numbers in a rangeMedium

Find two primes p < q with left ≤ p < q ≤ right and the smallest possible gap q − p. If several pairs tie, return the one with the smallest p. If no pair exists, return [-1, -1]. (1 ≤ left ≤ right ≤ 106.)

Example 1
Input: left = 10, right = 19
Output: [11, 13]
Explanation: Primes in range: 11, 13, 17, 19. Gaps 2, 4, 2. The first pair with the smallest gap is [11, 13].
Example 2
Input: left = 4, right = 6
Output: [-1, -1]
Explanation: Only one prime (5) in the range, so there is no pair.
Stuck? Open one hint at a time
Hint 1
right can be 106. Checking each number with the √n prime test works, but it repeats a lot of work.
Hint 2
Build one sieve up to right. Then walk through the primes in [left, right] and keep the smallest gap.
Show all 2 solutions with explanations
Approach 1Prime test for each number
The idea

Walk from left to right, test each number with the √n check, and compare each prime with the previous prime.

JavaScript
function isPrime(n) {  if (n < 2) return false;  for (let i = 2; i * i <= n; i++) if (n % i === 0) return false;  return true;}function closestPrimes(left, right) {  let prev = -1;  let best = [-1, -1];  for (let k = left; k <= right; k++) {    if (!isPrime(k)) continue;    if (prev !== -1 && (best[0] === -1 || k - prev < best[1] - best[0])) best = [prev, k];    prev = k;  }  return best;}console.log(closestPrimes(10, 19)); // [ 11, 13 ]console.log(closestPrimes(4, 6));   // [ -1, -1 ]
How it works

O(n√n) for a range of size n. For a range up to 106 that is about 109 steps in the worst case, which is too slow.

Approach 2Sieve once, then scan
The idea

Use the sieve to mark every prime up to right. Then do the same scan, but check the array instead of testing each number.

JavaScript
function closestPrimes(left, right) {  const isPrime = new Array(right + 1).fill(true);  isPrime[0] = false;  isPrime[1] = false;  for (let i = 2; i * i <= right; i++) {    if (!isPrime[i]) continue;    for (let j = i * i; j <= right; j += i) isPrime[j] = false;  }  let prev = -1;  let best = [-1, -1];  for (let k = left; k <= right; k++) {    if (!isPrime[k]) continue;    if (prev !== -1 && (best[0] === -1 || k - prev < best[1] - best[0])) best = [prev, k];    prev = k;  }  return best;}console.log(closestPrimes(10, 19)); // [ 11, 13 ]console.log(closestPrimes(4, 6));   // [ -1, -1 ]
How it works

The sieve takes O(n log log n) and the scan takes O(n). The space is O(n). The strict < keeps the first pair when two gaps are equal, as the question asks.

Which approach should you use?

When a problem needs many primes, build the sieve once. (LeetCode 2523.)

Make it stick

Make it stick — close this page, then
  1. Explain in two sentences why checking divisors up to √n is enough.
  2. Run the sieve by hand for n = 50 and list the primes you get.
  3. Write Euclid's GCD from memory, then dry-run gcd(84, 36).
  4. Write fast power from memory and dry-run power(2, 11).
  5. Say why (a * b) % M can be wrong in JavaScript and how to fix it.

Revisit schedule: solve two of the questions again tomorrow, again in 3 days, and once more in a week — without looking at the answers. Each time it will feel easier. That is the memory forming.

What's next

Several ideas in this lesson have a natural recursive form. gcd(a, b) = gcd(b, a % b) is a rule defined using itself, and so is xn = (xn/2)². Lesson 14 introduces recursion, which means a function that calls itself. It also explains the call stack, the list of active function calls that makes recursion work.

Practise on LeetCode

Real interview problems that use exactly what this lesson teaches. Solve them in JavaScript after the practice questions above.

Published by Jainik · Last updated · How these lessons are made

Keep learning

Related series