1

I had a quiz in my class and didn't do so well on it. I'm looking to find out if someone can explain to me what I did wrong here - our professor is overwhelmed with office hours as we moved online so I thought I'd post here.

def functionC(L):
    for i in range(len(L)):
        if L[i] == i:
            v = L.pop(i)
            L.insert(i, 2*v)
    return

I supplied the following answer:

The above function is O(n) because the for-loop grows with the size of L. The pop and insert functions are both constant time.

the word time is crossed out, but there is no other explanation as to why I received 6/10 for the question. What did I get wrong in this and why?

Here is an image of the question and my answer to prove the quiz has already been graded and handed back.

enter image description here

DMellon
  • 113
  • 3

2 Answers2

2

You need to be aware that the complexity of insert and pop can be $O(n)$ in python ($n = \text{length($L$)}$). Hence, the time complexity can be $O(n^2)$.

OmG
  • 3,572
  • 1
  • 14
  • 23
1

As pointed out by Yuval Filmus, you should ask that question to your professor.

I'll just point out some possible issues. I will also assume that $L$ is a python list.

  • $n$ is never defined, so it is unclear what $O(n)$ even means.

  • The wording "the for loop grows with the size of L" is a bit odd. A for loop does not grow. Its number of iterations is equal to the size (or better, length) of L.

  • From here, it seems that lists in python are implemented using arrays*. The $L.\mbox{pop}(i)$ operation requires $\Theta( 1 + \mbox{len}(L)-i )$ time** and, in your case, you can have $\Theta(\mbox{len}(L))$ iterations for which $\mbox{len}(L)-i = \Theta(\mbox{len}(L))$. The same holds for $L.\mbox{insert}(i, \cdot)$. The overall time complexity is then $O((\mbox{len}(L))^2)$. Consider, for example, the list $L = \langle 0, 1, \dots, \mbox{len}(L) \rangle$.

*These arrays are dynamically resized when elements are appended and not enough capacity is available. This only guarantees a constant amortized time per operation, so even a single operation on a list $L$ that you got as a parameter could cost up to $O(\mbox{len}(L))$ worst-case time. This is not a concern in your code since the initial capacity of $L$ is never exceeded.

**Some sources report that $L\mbox{.pop}(i)$ requires $O(i)$ time. The above argument still holds in this case.

Steven
  • 29,419
  • 2
  • 28
  • 49
  • 2
    O(1+n^2) is the same thing as O(n^2). – Acccumulation Apr 01 '20 at 02:51
  • 1
    Any sources that claim L.pop(i) takes O(i) time are wrong. pop needs to shift all elements that came after the popped element, not the elements that came before it. – user2357112 Apr 01 '20 at 03:08
  • Also, the resize strategy only guarantees constant amortized resize costs per operation - other costs of operations are not affected. – user2357112 Apr 01 '20 at 03:10
  • "n is never defined" - n is conventionally the size of the input. While that, and the phrasing problem (the second point), could still contribute to getting marked down, the fact that the complexity given in the answer is just wrong (what you mentioned in the third point) is probably the primary contributor here. – Bernhard Barker Apr 01 '20 at 04:01
  • 1
    While the resulting complexity given in this answer is right, you seem to just be silently dropping the i from the analysis, which you can't really do, as it may change the complexity. Normally you'd need to sum up the running time of each iteration of the loop and then using some series formula to determine the result, but in this case the time of the two operations actually add up to len(L), which makes it simpler. – Bernhard Barker Apr 01 '20 at 04:20
  • @Dukeling, I'm dropping the $i$ from the analysis because, as far as an upper bound is concerned, each of the $O(n)$ iterations requires at most $O(n)$ time. This is clearly not overestimating the complexity since it is easy to see that, in the worst case, at least $\frac{n}{2}$ iterations require $\Omega(n)$ time. – Steven Apr 01 '20 at 10:31