0

I know there are many other questions out there asking for the general guide of how to calculate the time complexity, such as this one.

From them I have learnt that when there is a loop, such as the (for... if...) in my Python programme, the Time complexity is N * N where N is the size of input. (please correct me if this is also wrong) (Edited once after being corrected by an answer)

# greatest common divisor of two integers 

a, b = map(int, input().split())
list = []

for i in range(1, a+b+1):
  if a % i == 0 and b % i == 0:
    list.append(i) 

n = len(list)
print(list[n-1])

However, do other parts of the code also contribute to the time complexity, that will make it more than a simple O(n) = N^2 ? For example, in the second loop where I was finding the common divisors of both a and b (a%i = 0), is there a way to know how many machine instructions the computer will execute in finding all the divisors, and the consequent time complexity, in this specific loop?

I wish the question is making sense, apologise if it is not clear enough.

Thanks for answering

cliff_leaf
  • 118
  • 10

2 Answers2

1

First, a few hints:

  1. In your code there is no nested loop. The if-statement does not constitute a loop.
  2. Not all nested loops have quadratic time complexity.
  3. Writing O(n) = N*N doesn't make any sense: what is n and what is N? Why does n appear on the left but N is on the right? You should expect your time complexity function to be dependent on the input of your algorithm, so first define what the relevant inputs are and what names you give them.
  4. Also, O(n) is a set of functions (namely those asymptotically bounded from above by the function f(n) = n, whereas f(N) = N*N is one function. By abuse of notation, we conventionally write n*n = O(n) to mean n*n ∈ O(n) (which is a mathematically false statement), but switching the sides (O(n) = n*n) is undefined. A mathematically correct statement would be n = O(n*n).
  5. You can assume all (fixed bit-length) arithmetic operations to be O(1), since there is a constant upper bound to the number of processor instructions needed. The exact number of processor instructions is irrelevant for the analysis.

Let's look at the code in more detail and annotate it:

a, b = map(int, input().split()) # O(1)
list = []                        # O(1)

for i in range(1, a+b+1):        # O(a+b) multiplied by what's inside the loop
  if a % i == 0 and b % i == 0:  # O(1)
    list.append(i)               # O(1) (amortized)

n = len(list)                    # O(1)
print(list[n-1])                 # O(log(a+b))

So what's the overall complexity? The dominating part is indeed the loop (the stuff before and after is negligible, complexity-wise), so it's O(a+b), if you take a and b to be the input parameters. (If you instead wanted to take the length N of your input input() as the input parameter, it would be O(2^N), since a+b grows exponentially with respect to N.)

Mo B.
  • 5,307
  • 3
  • 25
  • 42
  • Thank you so much for your answer! I've corrected some phrases in my question description. As you see from my use of language that I haven't yet taken a proper algorithm class in university (currently only learning from Coursera), so I'm really appreciative of detailed answers like this. Unfortunately I don't have enough reputation yet to vote for an answer – cliff_leaf Dec 08 '20 at 09:05
  • @cliff_leaf There was actually a bug in my last sentence - I said logarithmical when it should be exponential. Corrected now. – Mo B. Dec 08 '20 at 09:14
0

One thing to keep in mind, and you have the right idea, is that higher degree take precedence. So you can have a step that’s constant O(1) but happens n times O(N) then it will be O(1) * O(N) = O(N).

Your program is O(N) because the only thing really affecting the time complexity is the loop, and as you know a simple loop like that is O(N) because it increases linearly as n increases.

Now if you had a nested loop that had both loops increasing as n increased, then it would be O(n^2).

Joao
  • 1
  • 1