A loop has an inner loop that depends on the outer variable. Is it still quadratic, and how can you see that from the operation count?
It is still quadratic, because the inner loop never runs a full n times for every outer iteration. It runs the remaining distance instead, so the total is a triangular number rather than a square, and a triangular number is still proportional to n squared.
def count_pairs(n):
steps = 0
for i in range(n):
for j in range(i, n):
steps += 1
return steps
for n in (4, 8, 16):
print(n, count_pairs(n))The counts are 10, 36 and 136, which is n times n plus one, divided by two. Now look at what happens when n doubles: 10 becomes 36, and 36 becomes 136. The work grows by roughly four times each time n doubles, and four times the input is the signature of a quadratic bound. The halving of the constant does not change the growth rate, it only changes the factor in front.
The general rule for reading nested loops: if the inner bound is independent of the outer variable, the costs multiply. If the inner bound is derived from the outer variable, you have to add up the actual number of iterations, which often collapses to a single term once you simplify the arithmetic series. If the inner loop is conditional on data rather than on the index, you have an input-sensitive bound and should state the worst case.
The trade-off to mention is that constant factors hidden inside a quadratic are worth attacking only after the exponent is right. Turning the triangular loop into a genuine single pass changes the complexity class; removing a factor of two from the constant does not. In an interview, say which one you are doing.