Greatest Common Divisor (GCD) - FreeCodeCamp Daily Challenge
Greatest Common Divisor
In this post, we solve the “Greatest Common Divisor (GCD)” problem using the classic Euclid’s Algorithm. We’ll cover the mathematical idea, a recursive implementation (used in the solution), an iterative alternative, examples, tests, and visualizations.
📝 Problem Statement
Given two positive integers and , return their greatest common divisor (GCD), denoted as .
The GCD of two integers is the largest number such that and (i.e., divides both without remainder).
Example: divisors of ; divisors of . .
🔬 Mathematical Idea (Euclid’s Algorithm)
Euclid’s Algorithm relies on the following property:
If and are integers, with ,
and if then
That is: the GCD of is the same as the GCD of . By applying this repeatedly, we reduce the problem until the second number is .
📊 Process Diagram (Mermaid)
flowchart TB Start["Start: (a, b)"] Check["Is b === 0?"] ReturnA["Return a (gcd)"] Compute["Compute r = a % b"] Recurse["Repeat with (b, r)"] Start --> Check Check -- "yes" --> ReturnA Check -- "no" --> Compute Compute --> Recurse Recurse --> Check
✅ JavaScript Implementation (Recursive — Used Version)
This is the code found in the repository (the proposed solution):
/**
* FreeCodeCamp Problem: Gcd
* Category: FreeCodeCamp
*
* @param {number} x - First positive integer
* @param {number} y - Second positive integer
* @returns {number} The greatest common divisor of x and y
*/
function gcd(x, y) {
if (y === 0) {
return x
}
return gcd(y, x % y)
}
export default gcdStep by Step (Recursive)
- If
y === 0, returnx(base case). - Otherwise, call
gcd(y, x % y), quickly reducing the magnitude of the numbers. - The recursion ends when
yreaches 0.
🔁 Alternative: Iterative Implementation
The iterative version avoids recursion and uses a while loop. It works just as well and may be preferable when there is a risk of reaching recursion limits:
function gcdIterative(x, y) {
while (y !== 0) {
const temp = y
y = x % y
x = temp
}
return x
}🧪 Included Tests
The repository includes tests covering representative cases:
import { describe, expect, it } from 'vitest'
import gcd from './gcd'
describe('Gcd', () => {
it('gcd(4, 6) should return 2.', () => {
expect(gcd(4, 6)).toBe(2)
})
it('gcd(20, 15) should return 5.', () => {
expect(gcd(20, 15)).toBe(5)
})
it('gcd(13, 17) should return 1.', () => {
expect(gcd(13, 17)).toBe(1)
})
it('gcd(654, 456) should return 6.', () => {
expect(gcd(654, 456)).toBe(6)
})
it('gcd(3456, 4320) should return 864.', () => {
expect(gcd(3456, 4320)).toBe(864)
})
})These tests verify:
- small cases with a common divisor,
- when one number is a multiple of the other,
- relatively prime numbers (GCD = 1),
- medium-large numbers and cases with a large GCD.
🧮 Manual Example of the Algorithm
Let’s take :
[ \begin{align*} \gcd(654, 456) &\to \gcd(456, 198) \ \gcd(456, 198) &\to \gcd(198, 60) \ \gcd(198, 60) &\to \gcd(60, 18) \ \gcd(60, 18) &\to \gcd(18, 6) \ \gcd(18, 6) &\to \gcd(6, 0) = 6 \end{align*} ]
Result: .
📐 Complexity
- Time complexity: . The algorithm quickly reduces the operands; each step roughly halves the problem size.
- Space complexity:
- Recursive: , practically .
- Iterative: (constant).
⚠️ Edge Cases and Considerations
Some key points and special cases:
Domain: The problem is defined for positive integers .
Mathematical convention:
for any .
Equal numbers:
Relatively prime: If and are relatively prime (), then they have no common divisors except .
Negatives and decimals: The classic algorithm does not consider negatives or decimals. If needed, use and :
and validate that both are integers.
Large inputs: For very large values of and , the iterative version is preferable to avoid stack overflows from deep recursion.
Visual examples:
- (relatively prime)
🔍 Reflections and Learnings
- Euclid’s Algorithm is elegant, simple, and very efficient for this classic problem.
- Recursive implementations are shorter and more expressive; iterative ones are safer regarding the stack.
- Understanding the mathematical property () makes it easier to reason about the algorithm’s correctness.
📚 Resources and References
Would you like me to add a small benchmark (timings) between the recursive and iterative versions for very large numbers, or leave it as is?