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
- A little maths, a lot less work
- Digit problems revisited
- All divisors in √n steps
- Checking for a prime
- The Sieve of Eratosthenes
- GCD with Euclid's algorithm
- Traced: gcd(252, 105)
- LCM from the GCD
- Remainders and modular arithmetic
- Fast exponentiation
- The costs side by side
- Practice questions (9)
- Make it stick
- 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.
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 binaryAll 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.
| i (small) | 36 / i (large) | i × (36 / i) |
|---|---|---|
| 1 | 36 | 36 |
| 2 | 18 | 36 |
| 3 | 12 | 36 |
| 4 | 9 | 36 |
| 6 | 6 | 36 ← 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.
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.
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)); // falseTime 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.
- Start with every number from 2 to n marked as prime.
- Take the smallest number still marked. It is prime. Cross out all its multiples.
- Repeat. Stop once i × i is greater than n — every number left is prime.
| i | Still marked? | Crosses out (from i × i) |
|---|---|---|
| 2 | yes | 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30 |
| 3 | yes | 9, 12, 15, 18, 21, 24, 27, 30 |
| 4 | no — skip | |
| 5 | yes | 25, 30 |
| 6 | 6 × 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.
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:
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)
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));| a | 252 |
| b | 105 |
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).
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)); // 42For 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:
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) % MSo 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:
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 remainder2. 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:
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))); // 259106859The 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.
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| n | n odd? | result | x after squaring |
|---|---|---|---|
| 13 | yes → result × 3 | 3 | 9 |
| 6 | no | 3 | 81 |
| 3 | yes → result × 81 | 243 | 6,561 |
| 1 | yes → result × 6,561 | 1,594,323 | 43,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
| Task | Obvious way | With the right fact | The fact |
|---|---|---|---|
| Digits of n | — | O(log n) | One digit removed per pass |
| All divisors of n | O(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 n | O(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) |
| xn | O(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.
Return the greatest common divisor of the smallest and the largest number in nums.
Hint 1
Hint 2
gcd(a, b) = gcd(b, a % b).Show all 2 solutions with explanations
Count down from the smallest value. The first number that divides both values is the GCD.
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])); // 1This 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.
Find the minimum and maximum, then apply Euclid's rule.
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])); // 3Finding 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.
Use Euclid. It is just as short, and its speed hardly changes when the values are large. (LeetCode 1979.)
Return the smallest positive number that every number from 1 to n divides evenly (1 ≤ n ≤ 20). “Divides evenly” means the remainder is 0.
Hint 1
Hint 2
Show all 2 solutions with explanations
Test n, 2n, 3n, … until one is divisible by every number from 1 to n.
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)); // 2520This works for small n. For n = 20 it tests over 11 million candidates. The answer grows very fast, so this soon becomes too slow.
Start with 1 and add in each number one at a time: result = lcm(result, k).
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| k | gcd(result, k) | result after |
|---|---|---|
| 2 | 1 | 2 |
| 3 | 1 | 6 |
| 4 | 2 | 12 |
| 5 | 1 | 60 |
| 6 | 6 | 60 |
When k already divides the result (k = 6), the GCD equals k and the result does not change.
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.
Return true if n has exactly three positive divisors.
Hint 1
Hint 2
Hint 3
Show all 2 solutions with explanations
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.
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)); // falseO(√n) time, O(1) space. This uses the pairs idea from the lesson directly.
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.
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)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.
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.)
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.
Hint 1
Hint 2
Hint 3
Show all 2 solutions with explanations
Keep replacing n with the sum of the squares of its digits. Stop at 1 (happy) or when a value repeats (not happy).
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)); // falseWhy 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.
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.
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)); // falseO(1) extra space. This “fast and slow pointer” idea comes back in Lesson 36, where it finds loops in linked lists.
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.)
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.
Hint 1
Hint 2
Show the solution with explanations
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.
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)); // falseEach 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.
The loop “divide out a factor while you can” is the core of prime factorisation. The next question builds the full version. (LeetCode 263.)
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.
Hint 1
Hint 2
Show the solution with explanations
- For d = 2, 3, 4, … while
d * d <= n: divide out d as many times as possible, recording it each time. - If n is still greater than 1 at the end, what is left of n is itself a prime factor.
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 ]| d | divides out | n after |
|---|---|---|
| 2 | 2, 2, 2 | 45 |
| 3 | 3, 3 | 5 |
| 4 | — (3 × 3 … 4 × 4 = 16 > 5, stop) | 5 |
| after loop | n = 5 > 1 → push 5 |
Non-prime d (like 4) never divides n, because its prime factors were already divided out.
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.
Return the remainder of ab divided by 109 + 7 (written ab mod (109 + 7)), for 1 ≤ a ≤ 109 and 0 ≤ b ≤ 109.
Hint 1
Hint 2
% M after every multiplication.Hint 3
Show all 2 solutions with explanations
Use the fast-power loop from the lesson and take the remainder after each multiplication.
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 976371285The 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.
Use the same loop, but make every value a BigInt (a JavaScript type for whole numbers of any size), so products are exact.
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)); // 140625001O(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.
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.
Compute xn without using ** or Math.pow. Here x is a decimal number and n is a whole number that may be negative.
Hint 1
Hint 2
Show all 2 solutions with explanations
Multiply x into the result |n| times (|n| means n without its minus sign). For a negative n, take 1 divided by the result.
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.25O(n). With n around 2 × 109 this is billions of multiplications, which is far too slow for these limits.
Handle the sign first, then use repeated squaring (fast exponentiation).
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)); // 1594323O(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.
Approach 2 is the expected answer. Mention the negative exponent edge case before you write the loop. (LeetCode 50.)
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.)
Hint 1
Hint 2
right. Then walk through the primes in [left, right] and keep the smallest gap.Show all 2 solutions with explanations
Walk from left to right, test each number with the √n check, and compare each prime with the previous prime.
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 ]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.
Use the sieve to mark every prime up to right. Then do the same scan, but check the array instead of testing each number.
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 ]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.
When a problem needs many primes, build the sieve once. (LeetCode 2523.)
Make it stick
- Explain in two sentences why checking divisors up to √n is enough.
- Run the sieve by hand for n = 50 and list the primes you get.
- Write Euclid's GCD from memory, then dry-run gcd(84, 36).
- Write fast power from memory and dry-run power(2, 11).
- Say why
(a * b) % Mcan 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