temp
isn't a great name for your accumulator. Call it something like total
, which gives a much clearer indication of what role it plays.
First, let's separate the logic from the I/O, to make it easier to test:
def sum_3s_and_5s(n):
total = 0
for i in range(n):
if (i%3==0 or i%5==0):
total+=i
return total
if __name__ == "__main__":
print(sum_3s_and_5s(10))
We can now focus on the implementation of sum_3s_and_5s
. We can write the same algorithm more succinctly:
def sum_3s_and_5s(n):
return sum([i * (i%3==0 or i%5==0) for i in range(n)])
This doesn't change the time complexity, of course - it still scales as O(n). What we need to do is think about the mathematics. What we're looking for is the sum of all the multiples of 3 in the range, added to all the multiples of 5, less the multiples of 15 (which are otherwise double-counted). Let's write a function to count multiples of k in a given range:
def sum_multiples(n, k):
return sum([i * (i%k==0) for i in range(n)])
def sum_3s_and_5s(n):
return sum_multiples(n,3) + sum_multiples(n,5) - sum_multiples(n, 15)
You might not think that's an improvement - and you'd be right, because we're now reading the whole range three times instead of once. But we can work or sum_multiples
, to make it O(1). Remember the formula for triangular numbers: sum(i){0,n} = ½n(n+1). The sum of all the multiples of k is just k times the sum of integers from 0 to n/k. That gives us:
def sum_multiples(n, k):
# subtract 1 from n, as range is exclusive
n = (n-1) // k
return k * n * (n + 1) // 2
Now it doesn't matter how big n gets, the calculation is constant time (or perhaps O(log n) if we get into big integers).
RuntimeError
and just generates a time-out, that part would be on-topic (and you should add the tag time-limit-exceeded). \$\endgroup\$