Project Euler Solutions in TheAlgorithms/Python
The first seven Project Euler problems, each solved in a single Python function with doctests, showing how mathematical insight reduces brute force to a handful of lines.
What you will learn
- How a generator expression with a conditional filter sums multiples of 3 or 5 below 1000 in one line
- How two-pointer Fibonacci generation accumulates even terms without storing the full sequence
- How is_prime uses the 6k plus-or-minus-1 rule to check primality in O(sqrt(n)) without a precomputed sieve
- How string reversal detects palindromes and why iterating candidate products from the top down finds the largest first
- Why the brute-force LCM approach increments by n times (n-1) to skip non-multiples of the largest factor
- How the sum of squares and square of sum each reduce to O(n) loops, making their difference computable directly
- How the 6k plus-or-minus-1 optimization in is_prime reduces the inner loop to one-third of candidate divisors
Prerequisites
- Familiarity with Python generators, loops, and basic math functions
- Understanding of what a prime number is
- No prior knowledge of any Euler problem required
Problem 1: sum multiples of 3 or 5 below 1000 with a generator expression
project_euler/problem_001/sol1.py:13A generator expression with a modulo filter scans the range once and accumulates qualifying values without building an intermediate list.
Project Euler Problem 1 asks for the sum of all multiples of 3 or 5 below 1000. The brute-force approach scans every integer below the limit, checks divisibility by 3 or 5, and accumulates the total. This file implements that in one expression: sum(e for e in range(3, n) if e % 3 == 0 or e % 5 == 0). The generator produces qualifying integers lazily without a list in memory, and Python's built-in sum accumulates them in a single pass. Starting the range at 3 skips 0, 1, and 2. The doctest confirms the problem's own example: multiples below 10 are 3, 5, 6, and 9, summing to 23. A more efficient solution uses the triangular number formula in O(1), but the generator version is easier to verify directly against the problem description.
A generator filter over range(3, n) with a modulo condition accumulates qualifying multiples into sum in a single lazy pass without a list.
def solution(n: int = 1000) -> int:
"""
Returns the sum of all the multiples of 3 or 5 below n.
>>> solution(3)
0
>>> solution(4)
3
>>> solution(10)
23
>>> solution(600)
83700
>>> solution(-7)
0
"""
return sum(e for e in range(3, n) if e % 3 == 0 or e % 5 == 0)Problem 2: sum even Fibonacci terms with two-pointer iteration
project_euler/problem_002/sol1.py:19Two-pointer Fibonacci generation produces each term in O(1) space by swapping variables; filtering on even values adds no overhead.
Project Euler Problem 2 asks for the sum of even Fibonacci terms below four million. The two-pointer technique stores only two consecutive Fibonacci values at a time, avoiding the full sequence in memory. i and j start at 1 and 2, the first two terms by the problem's convention. Each iteration checks whether j is even before adding it to the total, then updates both variables in one assignment: i, j = j, i + j. This simultaneous update is correct because Python evaluates the right-hand side fully before any assignment occurs, so the new j = i + j uses the old i. The loop condition j <= n stops when the next term exceeds the limit. The doctest confirms that even Fibonacci terms up to 34 are 2, 8, and 34, summing to 44, matching the expected result.
The two-pointer update i, j = j, i + j advances the Fibonacci sequence in O(1) space; the even check adds one modulo operation per iteration without changing the structure.
def solution(n: int = 4000000) -> int:
"""
Returns the sum of all even fibonacci sequence elements that are lower
or equal to n.
>>> solution(10)
10
>>> solution(15)
10
>>> solution(2)
2
>>> solution(1)
0
>>> solution(34)
44
"""
i = 1
j = 2
total = 0
while j <= n:
if j % 2 == 0:
total += j
i, j = j, i + j
return totalProblem 3: find the largest prime factor using trial division up to sqrt(n)
project_euler/problem_003/sol1.py:17Trial division up to sqrt(n) suffices for primality because any composite number has a factor at or below its square root.
Problem 3 asks for the largest prime factor of 600,851,475,143. The approach combines a primality test with trial division. The is_prime function implements the 6k plus-or-minus-1 optimization: all primes greater than 3 are of the form 6k+1 or 6k-1, so the trial division loop steps by 6 and tests two candidates per step. This reduces the number of candidates by roughly one-third compared to testing all odd numbers. The loop only runs to the square root of the number because any composite has at least one factor at or below its square root. The solution function strips factors of 2, then scans odd candidates and tests each with is_prime. The doctest confirms the factors of 13195 include the largest prime 29.
The 6k plus-or-minus-1 rule cuts trial division candidates by one-third; stopping at sqrt(n) is correct because composite factors always have a factor below the root.
def is_prime(number: int) -> bool:
"""Checks to see if a number is a prime in O(sqrt(n)).
A number is prime if it has exactly two factors: 1 and itself.
Returns boolean representing primality of given number (i.e., if the
result is true, then the number is indeed prime else it is not).
>>> is_prime(2)
True
>>> is_prime(3)
True
>>> is_prime(27)
False
>>> is_prime(2999)
True
>>> is_prime(0)
False
>>> is_prime(1)
False
"""
if 1 < number < 4:
# 2 and 3 are primes
return True
elif number < 2 or number % 2 == 0 or number % 3 == 0:
# Negatives, 0, 1, all even numbers, all multiples of 3 are not primes
return False
# All primes number are in format of 6k +/- 1
for i in range(5, int(math.sqrt(number) + 1), 6):
if number % i == 0 or number % (i + 2) == 0:
return False
return TrueProblem 4: find the largest palindrome product by string reversal
project_euler/problem_004/sol1.py:16Iterating palindrome candidates from the largest downward means the first palindrome that factors into two 3-digit numbers is the answer.
Problem 4 asks for the largest palindrome made from the product of two 3-digit numbers. The approach iterates candidate numbers from the largest possible product (999 times 999, which equals 998,001) downward, tests each for palindrome structure, and then checks whether it has a 3-digit divisor. A palindrome check on a string is a single comparison: str_number == str_number[::-1] reverses the string with slice notation and compares the result. When a palindrome is found, the inner loop scans divisors from 999 downward, checking both that the divisor evenly divides the palindrome and that the resulting quotient is also a 3-digit number. Descending order guarantees the first match is the largest. The default parameter n = 998001 is the maximum possible 3-digit product. The doctests verify three smaller upper bounds to validate the descent logic.
str_number == str_number[::-1] detects palindromes in one expression; counting down from the maximum ensures the first palindrome with a 3-digit divisor pair is the largest.
def solution(n: int = 998001) -> int:
"""
Returns the largest palindrome made from the product of two 3-digit
numbers which is less than n.
>>> solution(20000)
19591
>>> solution(30000)
29992
>>> solution(40000)
39893
>>> solution(10000)
Traceback (most recent call last):
...
ValueError: That number is larger than our acceptable range.
"""
# fetches the next number
for number in range(n - 1, 9999, -1):
str_number = str(number)
# checks whether 'str_number' is a palindrome.
if str_number == str_number[::-1]:
divisor = 999
# if 'number' is a product of two 3-digit numbers
# then number is the answer otherwise fetch next number.
while divisor != 99:
if (number % divisor == 0) and (len(str(number // divisor)) == 3.0):
return number
divisor -= 1
raise ValueError("That number is larger than our acceptable range.")Problem 5: smallest multiple divisible by all numbers 1 to 20
project_euler/problem_005/sol1.py:17Incrementing by n times (n-1) skips values that are not multiples of the two largest factors, reducing the search space before checking all divisors.
Problem 5 asks for the smallest number evenly divisible by every integer from 1 to 20, the least common multiple of 1 through 20. The mathematical solution applies the LCM formula. This implementation uses a brute-force search with one key optimization: it increments i by n * (n - 1) each step, which is 380 for the default case. Any number divisible by both n and n-1 must be a multiple of their product when they share no common factors, so this step skips non-candidates before checking all divisors. The inner loop then tests divisibility from 2 to n-1. When all pass, nfound stays 0 and the function returns. The doctest confirms 2520 for n=10, cited in the original problem statement.
Incrementing by n * (n - 1) pre-filters candidates that cannot be divisible by both largest factors, reducing inner-loop checks without computing the LCM formula directly.
def solution(n: int = 20) -> int:
"""
Returns the smallest positive number that is evenly divisible (divisible
with no remainder) by all of the numbers from 1 to n.
>>> solution(10)
2520
>>> solution(15)
360360
>>> solution(22)
232792560
>>> solution(3.4)
6
>>> solution(0)
Traceback (most recent call last):
...
ValueError: Parameter n must be greater than or equal to one.
>>> solution(-17)
Traceback (most recent call last):
...
ValueError: Parameter n must be greater than or equal to one.
>>> solution([])
Traceback (most recent call last):
...
TypeError: Parameter n must be int or castable to int.
>>> solution("asd")
Traceback (most recent call last):
...
TypeError: Parameter n must be int or castable to int.
"""
try:
n = int(n)
except TypeError, ValueError:
raise TypeError("Parameter n must be int or castable to int.")
if n <= 0:
raise ValueError("Parameter n must be greater than or equal to one.")
i = 0
while 1:
i += n * (n - 1)
nfound = 0
for j in range(2, n):
if i % j != 0:
nfound = 1
break
if nfound == 0:
if i == 0:
i = 1
return i
return NoneProblem 6: sum square difference computed directly with two O(n) loops
project_euler/problem_006/sol1.py:20Two accumulators in a single loop compute both the sum of squares and the sum of integers, making the difference calculation a one-line subtraction.
Problem 6 asks for the difference between the square of the sum and the sum of squares of the first 100 natural numbers. The problem statement gives the example for n=10: the sum of squares is 385, the square of the sum is 3025, and the difference is 2640. This solution accumulates two values simultaneously in one loop: sum_of_squares adds i**2 at each step, and sum_of_ints adds i. After the loop, squaring sum_of_ints and subtracting sum_of_squares gives the answer. A closed-form version uses the triangular number formula for the sum (n times (n+1) divided by 2) and the pyramidal number formula for the sum of squares (n times (n+1) times (2n+1) divided by 6), reducing the computation to O(1) arithmetic. The iterative version here trades that compactness for readability, and the doctest verifies four values including the 100-case answer.
Two accumulators in one loop collect both quantities simultaneously; squaring the sum accumulator after the loop avoids any list or second pass.
def solution(n: int = 100) -> int:
"""
Returns the difference between the sum of the squares of the first n
natural numbers and the square of the sum.
>>> solution(10)
2640
>>> solution(15)
13160
>>> solution(20)
41230
>>> solution(50)
1582700
"""
sum_of_squares = 0
sum_of_ints = 0
for i in range(1, n + 1):
sum_of_squares += i**2
sum_of_ints += i
return sum_of_ints**2 - sum_of_squaresProblem 7: find the 10001st prime by counting prime numbers up to it
project_euler/problem_007/sol1.py:52Counting primes by incrementing a candidate number and testing primality until a target count is reached finds the nth prime without precomputing a sieve.
Problem 7 asks for the 10,001st prime number. This file finds it by counting: start at 1, increment, test each number for primality, and stop when the count reaches the target. The implementation uses two loops to handle the small-prime bootstrap cleanly. The first loop increments by 1 while the number is below 3, covering 2 and 3 as special cases. The second loop increments by 2, skipping even numbers entirely, because all primes above 2 are odd. The is_prime function (defined at line 18) applies the same 6k plus-or-minus-1 optimization as in Problem 3: it eliminates multiples of 2 and 3 upfront and then tests candidates at positions 5, 11, 17, 23... (6k-1) and 7, 13, 19, 25... (6k+1). The doctest confirms the 6th prime is 13 and the 100th is 541, both verifiable against prime tables.
Incrementing by 2 skips even candidates; the 6k plus-or-minus-1 check in is_prime then reduces each test to roughly sqrt(n) divided by 3 candidates.
def solution(nth: int = 10001) -> int:
"""
Returns the n-th prime number.
>>> solution(6)
13
>>> solution(1)
2
>>> solution(3)
5
>>> solution(20)
71
>>> solution(50)
229
>>> solution(100)
541
"""
count = 0
number = 1
while count != nth and number < 3:
number += 1
if is_prime(number):
count += 1
while count != nth:
number += 2
if is_prime(number):
count += 1
return numberYou've walked through 7 key areas of the The Algorithms - Python codebase.
Next project: Warp → Browse all projectsCreate code tours for your project
Intraview lets AI create interactive walkthroughs of any codebase. Install the free VS Code extension and generate your first tour in minutes.
Install Intraview Free