Optimization: Code movement
You are repeatedly doing the same computations over and over. How many times is \$i^3\$ computed?
for j in range(1, n+1):
for k in range(1, n+1):
if i**3 + j**3 + k**3 == n:
return True
Inside these loops, the value of i
does not change. With j
and k
ranging over 100 values each, that is 10,000 redundant calculations! Since \$i^3\$ is constant, you can move that out of the loop. Ditto for \$j^3\$ and the middle loop:
n_plus_1 = n + 1
for i in range(1, n_plus_1):
i3 = i ** 3
for j in range(1, n_plus_1):
i3_plus_j3 = i3 + j ** 3
for k in range(1, n_plus_1):
if i3_plus_j3 + k ** 3 == n:
return True
Optimization: Search Space
You're looking for
$$i^3 + j^3 + k^3 = n, 1 \le i \le 100, 1 \le j \le 100, 1 \le k \le 100$$
If, after you've exhausted searching \$i = 1\$, and not finding any values of j
or k
which work, is there any point in exploring \$j = 1\$ or \$k = 1\$ for \$i \gt 1\$? No! That would just be a permutation of a triplet you've already tried.
Without loss of generality, you can search a much smaller space:
$$i^3 + j^3 + k^3 = n, 1 \le i \le j \le k \le 100$$
As in:
n_plus_1 = n + 1
for i in range(1, n_plus_1):
i3 = i ** 3
for j in range(i, n_plus_1):
i3_plus_j3 = i3 + j ** 3
for k in range(j, n_plus_1):
if i3_plus_j3 + k ** 3 == n:
return True
Optimization: Early termination
If \$i^3 + j^3 + k^3 \gt n\$, then trying with a larger value of k
won't result in a smaller value, so you can stop the inner loop at that point.
Similarly, if \$i^3 + j^3 + j^3 \gt n\$, then there is no point in even entering the inner loop, and larger values of j
are also pointless, so you can stop the middle loop at that point.
Finally, if \$i^3 + i^3 + i^3 \gt n\$, then there is no point in even entering the middle loop, and larger values of i
are also pointless, so you can stop the outer loop.
n_plus_1 = n + 1
for i in range(1, n_plus_1):
i3 = i ** 3
if 3 * i3 > n:
break
for j in range(i, n_plus_1):
j3 = j ** 3
if i3 + 2 * j3 > n:
break
i3_plus_j3 = i3 + j3
for k in range(j, n_plus_1):
i3_plus_j3_plus_k3 = i3_plus_j3 + k ** 3
if i3_plus_j3_plus_k3 > n:
break
if i3_plus_j3_plus_k3 == n:
return True
Optimization: Loop end-points
The above added a lot of if statements to the search, complicating the algorithm. We can do math to determine the actual endpoints, and remove the if conditions from inside the loops. The inner loop completely. vanishes.
$$ 3 * i^3 > n \rightarrow i_{max} = \lfloor \sqrt[3]{n / 3} \rfloor $$
$$ i^3 + 2 * j^3 > n \rightarrow j_{max} = \lfloor \sqrt[3]{\frac{n - i^3} {2}} \rfloor $$
$$ i^3 + j^3 + k^3 = n \rightarrow k = \sqrt[3]{n - i^3 - j^3} $$
THIRD = 1 / 3
i_max = int((n / 3) ** THIRD)
for i in range(1, i_max + 1):
i3 = i ** 3
j_max = int(((n - i3) / 2) ** THIRD)
for j in range(i, j_max + 1):
j3 = j ** 3
k = int((n - i3 - j3) ** THIRD)
if i3 + j3 + k ** 3 == n:
return True
Optimization: Precomputed Values
As vnp mentions, we can simplify/remove the math in the last step by precomputing cubes. Storing them in a set allows for \$O(1)\$ lookup.
THIRD = 1 / 3
k_max = int((n - 2) ** THIRD)
k_cubes = {k ** 3 for k in range(1, k_max + 1)}
i_max = int((n / 3) ** THIRD)
for i in range(1, i_max + 1):
i3 = i ** 3
j_max = int(((n - i3) / 2) ** THIRD)
if any((n - i3 - j ** 3) in k_cubes for j in range(i, j_max + 1)):
return True
Alternate Approach
Instead of asking "if 1 is the sum of 3 cubes", and then "if 2 is the sum of 3 cubes", and then "if 3 is the sum of 3 cubes" ... and so on up to "if 100 is the sum of 3 cubes", turn the problem around, and record a set of the sums of 3 cubes. The length of that set is the answer.
def cube_sums(n: int) -> set[int]:
i_max = int((n / 3) ** (1 / 3)) + 1
j_max = int(((n - 1) / 2) ** (1 / 3)) + 1
k_max = int((n - 2) ** (1 / 3)) + 1
return {t for i in range(1, i_max)
for j in range(i, j_max)
for k in range(j, k_max)
if (t := i ** 3 + j ** 3 + k ** 3) <= n}
cubes = cube_sums(100)
print(len(cubes))