Skip to content
CCC Python Course

Complexity analysis and deriving the target from N

Module
M2.9
Lesson
1 of 1
Reading time
5 min

In this lesson

  • Estimate how many operations a plan performs as N grows, and name that growth using Big-O.
  • Read a bound on N and judge, roughly, which complexity classes will finish in time and which will not.
  • Recognize that operation-per-second budgets and memory estimates are rules of thumb, not guarantees, and that a maximum-size test is still how you check.

A correct plan is not automatically a fast enough one. Every subtask carries a time limit, and a bound on N large enough to punish a slow plan even when that plan gives the right answer on every test. Complexity analysis is how you judge whether a plan will finish in time, from the bound alone, before you have written or run a single line of it.

Counting operations as N grows

Big-O describes how the number of operations a plan performs grows as N grows, ignoring constant factors and smaller terms. A single loop over N items performs about N operations: this is O(N). Two separate loops, each over N items, still add up to about 2N operations, which Big-O also calls O(N), since doubling a constant does not change the shape of the growth. A loop nested inside another loop, each running N times, performs about N * N operations: this is O(N^2).

Chart, step 1 of 40200,000400,000600,000800,0001,000,0002004006008001,000stepsn (size of the input)n²n log nn
Speed
Each line shows how many steps a program would take for an input of size n, if its work grows like n, n log₂ n, n².
  • Current

Figure 1How three growth rates compare as N grows from 1 to 1000

Read the steps as text

A chart of n, n log₂ n, n² for n from 1 to 1000. The faster-growing lines leave the chart early.

  1. Each line shows how many steps a program would take for an input of size n, if its work grows like n, n log₂ n, n².
  2. At n = 10: n is 10, n log₂ n is 33.2, n² is 100.
  3. At n = 100: n is 100, n log₂ n is 664.4, n² is 10,000.
  4. At n = 1000: n is 1,000, n log₂ n is 9,965.8, n² is 1,000,000.

N and N log N stay close together and grow gently. N^2 pulls far ahead of both once N passes a few hundred, and keeps pulling ahead faster the larger N gets. A plan whose operation count is O(N^2) can be fast enough for a small bound and still time out on a large one, even though the code never changes.

From a plan to a complexity

examples/count_pairs.py
n = int(input())
count = 0for i in range(n):    for j in range(i + 1, n):        count += 1
print(count)

Input

4

Output

6
Counting every distinct pair of positions, the same definition of a pair the module on brute force uses

The outer loop runs n times. For each of those, the inner loop runs fewer times than the last, from close to n down to 0, but its length still grows in step with n. Dropping the constant factor that comes from that shrinking, the body still runs on the order of n * n times in total. That count is O(n^2). A plan with one loop over the input, doing a fixed amount of work per item, is O(n) instead. Sorting a list of n items is O(n log n), since a comparison-based sort compares roughly n log n pairs of items in the worst case, not the n^2 pairs a nested loop would check.

Reading a bound as a target complexity

A problem's bound on N tells you, roughly, which complexity a plan needs to have to finish in time. These figures are rules of thumb from how contest judges tend to be set up, not guarantees for any one problem:

Bound on NTarget complexity
N ≤ 10O(N!) or O(2^N · N)
N ≤ 20O(2^N)
N ≤ 400–500O(N^3)
N ≤ 5000O(N^2)
N ≤ 2·10^5 – 10^6O(N log N) or O(N)
N ≥ 10^9O(log N), O(√N), or O(1) math

Reading this table runs in the opposite direction from writing a plan. Once you have a bound, this table tells you roughly what complexity a working plan needs, before you have designed that plan. A bound of N ≤ 1000 allows an O(N^2) plan, since 1000^2 is one million, comfortably within what a judge can run in time. The same O(N^2) plan applied to a bound of N ≤ 10^6 would need about 10^{12} operations, far beyond what any judge finishes in time.

PyPy, which the grader runs, does roughly 10^7 to 10^8 simple operations per second. Plain CPython does roughly 10 to 50 times fewer. Both of these numbers are estimates, not exact figures, since the real cost also depends on what each operation does. Treat the table and these figures as a first estimate, not as proof. The module on testing already covers building a maximum-size test and timing it. That test is still the way to check a plan finishes in time, on the machine you have.

The cost of common built-ins

Some operations cost more than they look like they should, and knowing their rough cost helps you count operations accurately. Looking up a list by index, or a dictionary by key, costs O(1): roughly the same small amount of work no matter how large the list or dictionary is. Adding an item to the end of a list also costs O(1), on average. Checking whether a value appears anywhere in a list costs O(n), since it may need to check every item before it can be sure. The same check against a set or a dictionary's keys costs O(1) on average, since neither has to scan every item to answer. Sorting a list costs O(n log n).

Memory as a limit too

A judge also limits how much memory a program may use, commonly 512 megabytes. This rarely matters for small bounds, but it becomes real once N reaches the hundreds of millions. A list of N integers, on PyPy, costs roughly 8 bytes per integer, as a rule of thumb rather than an exact figure. A list of ten million integers costs roughly eighty megabytes: comfortably inside the limit. A list of a billion integers would cost roughly eight gigabytes: far past it. That is a sign a plan needs to avoid holding every value in memory at once, however fast its operations might be.

Recap

Big-O describes how a plan's operation count grows with N, ignoring constant factors. Examples include O(N) for one pass over the input, O(N log N) for a sort, and O(N^2) for a loop nested inside another loop. A problem's bound on N points, roughly, at which of these a working plan needs to reach, using the target-complexity table above. PyPy's rough operations-per-second budget and a rough memory-per-value figure turn a bound into an estimate, but both stay only estimates. A maximum-size test, run and timed on your own machine, is still what actually confirms it.

Practice

Try these on the judges. Each link opens the problem on WMOJ or DMOJ.

  1. 2025 J4
    Sunny Days (opens on WMOJ in a new tab) WMOJ

    Given N days of weather with exactly one day allowed to be corrected, find the longest run of sunny days that correction can produce, for N as large as 500,000.

  2. 2024 S1
    Hat Circle (opens on WMOJ in a new tab) WMOJ

    Given people seated in a circle wearing numbered hats, count how many see a matching number directly across the circle, for N as large as 1,000,000.

  3. 2019 J4
    Flipper (opens on DMOJ in a new tab) DMOJ(same problem as 2019 S1)

    Given a 2×2 grid of four numbers and a long string of flip instructions, apply every instruction in order and print the grid that results.

    Why DMOJ: A simulation problem, included here for extra practice.