Project Euler Problem 21 Solution

Project Euler Problem 21 Solution

Amicable numbers

by {BetaProjects} | Project Euler & HackerRank

Project Euler Problem 21 Statement

Let d(n) be defined as the sum of proper divisors of n (numbers less than n which divide evenly into n).

If d(a) = b and d(b) = a, where ab, then a and b are an amicable pair and each of a and b are called amicable numbers.

For example, the proper divisors of 220 are 1, 2, 4, 5, 10, 11, 20, 22, 44, 55 and 110; therefore d(220) = 284. The proper divisors of 284 are 1, 2, 4, 71 and 142; so, d(284) = 220.

Evaluate the sum of all the amicable numbers under 10000.

Solution

Assumption: Both parts of the amicable pair must lie below the limit. If they straddle the limit then we ignore them.

By finding a fast way to sieve the sum of proper divisors, we solve both this problem and the somewhat more challenging HackerRank version.

Sieve Method for Finding Sum of Proper Divisors

import math
L = 10000
ds = [1]*L
for i in range(2, math.isqrt(L)):
    for j in range(i+1, L//i):
        ds[i*j]+= i+j

The nested loop calculates the sum of proper divisors for each number up to L:

  1. The outer loop iterates over numbers i starting from 2 up to the square root of L. This ensures we only compute necessary divisors to avoid redundant calculations.
  2. The inner loop iterates over multiples of i:
    • j starts from $i + 1$ and goes up to the largest integer j where $i \times j \lt L$.
    • For each pair of factors $(i,j)$, i and j are proper divisors of the product $i \times j$.
    • ds[i * j]+= i + j adds i and j for $i \times j$, building up the sum of proper divisors for that product.

After this loop completes, ds[i] contains the sum of proper divisors for each integer i up to L.

Finding Amicable Pairs

amicableNumbers = []
for i in range(2, L):
    if ds[i] < i and ds[ds[i]] == i: 
        amicableNumbers.extend([ds[i], i])

HackerRank version

HackerRank Project Euler 21 extends the limit to $1\le N\le10^5$ and runs up to a thousand test cases. This requires us to pre-calculate the sum of divisors.

Python Source Code

import math
L = 100000
ds = [1]*L
for i in range(2, math.isqrt(L)):
    for j in range(i+1, L//i):
        ds[i*j]+= i+j
amicableNumbers = []
for i in range(2, L):
    if ds[i] < i and ds[ds[i]] == i: 
        amicableNumbers.extend([i,ds[i]])

for _ in range(int(input())):
    N = int(input())
    print(sum(a for a in amicableNumbers if a < N))	

Last Word

Sum of Amicable Numbers < Limit
Limit Run Time
Seconds
Result
104 0.008 31,626
105 0.012 852,810
106 0.042 27,220,963
107 0.975 649,734,295
108 15.6 11,526,067,720