Complexity & Constraints¶
In one sentence
Before writing code, estimate how many steps your idea takes for the largest input. Compare that with about \(10^7\) simple Python steps per second, and the constraints will tell you which algorithm you need.
1. What problem does it solve?¶
Example — 1520D · Same Differences (1-Set · C, 4-Misc · J, rating 1200)
You are given an array \(a_1, \dots, a_n\). Count the pairs of indices \((i, j)\) with \(i < j\) and \(a_j - a_i = j - i\).
Limits: \(t \le 10^4\) test cases, \(n \le 2 \cdot 10^5\), the sum of \(n\) over all test cases \(\le 2 \cdot 10^5\).
Input Output
4
6
3 5 1 4 6 6 1
3
1 2 3 3
4
1 3 3 4 3
6
1 6 3 4 5 6 10
Two correct ideas, with very different running times:
| idea | steps for \(n = 2 \cdot 10^5\) | verdict |
|---|---|---|
| try every pair \(i < j\) and check the equation | \(\frac{n(n-1)}{2} \approx 2 \cdot 10^{10}\) | way too slow |
| rewrite the equation, then count with a dictionary | \(n = 2 \cdot 10^5\) | fast |
The rewrite. Move \(i\) and \(j\) to the sides they belong to:
So define \(b_i = a_i - i\). A pair is good exactly when \(b_i = b_j\). Now we just count equal values, in one pass.
Both ideas give the right answer. Only the step count decides whether it passes, and you can know that before coding.
2. The math¶
2.1 Counting steps: Big-O¶
We count how the number of basic steps grows with the input size \(n\), and keep only the fastest-growing term, without constants:
- \(3n + 5\) steps → \(O(n)\)
- \(n^2 / 2 + 100n\) steps → \(O(n^2)\)
- two loops one after the other, each \(O(n)\) → \(O(n)\)
- a loop inside a loop, each \(O(n)\) → \(O(n^2)\)
- halving a range until it is empty → \(O(\log n)\)
Why drop constants? For large \(n\) the shape dominates: at \(n = 10^5\), \(100n = 10^7\) while \(n^2 = 10^{10}\).
2.2 Growth table¶
| \(n\) | \(\log_2 n\) | \(n\) | \(n \log_2 n\) | \(n^2\) | \(2^n\) |
|---|---|---|---|---|---|
| 10 | 3 | 10 | 33 | 100 | 1024 |
| \(10^3\) | 10 | \(10^3\) | \(10^4\) | \(10^6\) | — |
| \(10^5\) | 17 | \(10^5\) | \(1.7 \cdot 10^6\) | \(10^{10}\) | — |
| \(2 \cdot 10^5\) | 18 | \(2 \cdot 10^5\) | \(3.5 \cdot 10^6\) | \(4 \cdot 10^{10}\) | — |
| \(10^9\) | 30 | \(10^9\) | — | — | — |
2.3 From constraints to algorithm¶
CPython runs roughly \(10^7\) simple steps per second. PyPy is often \(5\)–\(10 \times\) faster. A typical limit is \(1\)–\(2\) seconds, so aim for at most about \(10^7\) steps in CPython.
| largest \(n\) | allowed | typical algorithms |
|---|---|---|
| \(\le 10\) | \(O(n!)\) | try all permutations |
| \(\le 20\) | \(O(2^n)\) | try all subsets, backtracking |
| \(\le 500\) | \(O(n^3)\) | three nested loops, small DP |
| \(\le 5000\) | \(O(n^2)\) | two nested loops, LCS-style DP |
| \(\le 2 \cdot 10^5\) | \(O(n \log n)\) | sorting, binary search, heaps, Dijkstra |
| \(\le 10^6\) | \(O(n)\) | prefix sums, two pointers, BFS/DFS, counting with a dict |
| \(\ge 10^9\) | \(O(\log n)\) or \(O(1)\) | a formula, binary search on the answer |
Read the constraints first. Same Differences says \(n \le 2 \cdot 10^5\): the table rules out \(O(n^2)\) before you write a line.
2.4 "The sum of \(n\) over all test cases"¶
With \(t = 10^4\) tests you might fear \(10^4 \cdot 2 \cdot 10^5\). But the statement caps the sum of all \(n\) at \(2 \cdot 10^5\). So an \(O(n)\) solution per test costs \(O(\sum n) = O(2 \cdot 10^5)\) in total. Many Codeforces problems have this line. Look for it.
2.5 What Python operations cost¶
| operation | cost | note |
|---|---|---|
a[i], a.append(x), a.pop() |
\(O(1)\) | |
a.pop(0), a.insert(0, x) |
\(O(n)\) | use collections.deque |
x in a for a list |
\(O(n)\) | scans the list |
x in s for a set / dict, d[x], d.get(x, 0) |
\(O(1)\) average | |
a.sort(), sorted(a) |
\(O(n \log n)\) | |
a[l:r] (slicing) |
\(O(r - l)\) | it copies |
sum(a), min(a), max(a), a.count(x) |
\(O(n)\) | each call walks the list |
s = s + c in a loop (strings) |
\(O(\text{len}(s))\) each time | builds a new string, so \(O(n^2)\) overall |
heapq.heappush / heappop |
\(O(\log n)\) |
The classic hidden \(O(n^2)\): calling an \(O(n)\) operation (in on a list, sum, count, pop(0), slicing) inside a loop of \(n\) iterations. For example, a.count(b[i]) for every \(i\) in Same Differences is \(O(n^2)\) again.
2.6 Amortized cost¶
Sometimes one step is expensive, but all steps together are cheap. In the sliding window, the inner while can run many times in one iteration, but left moves at most \(n\) times in the whole program, so the total is \(O(n)\). Count the total movement, not the worst single iteration.
2.7 Memory¶
A typical limit is \(256\) MB. A Python list of \(10^6\) small ints uses about \(8\) MB for the list. A \(5000 \times 5000\) 2D list is \(2.5 \cdot 10^7\) entries, about \(200\) MB: too close. Also remember that deep recursion stores one call frame per level.
3. Worked solution — Same Differences¶
Walk left to right. cnt[b] is how many earlier indices had \(a_i - i = b\). Each of them forms a good pair with the current index.
import sys
input = sys.stdin.readline
def solve():
n = int(input())
a = list(map(int, input().split()))
cnt = {}
ans = 0
for i in range(n):
b = a[i] - i
ans += cnt.get(b, 0)
cnt[b] = cnt.get(b, 0) + 1
print(ans)
t = int(input())
for _ in range(t):
solve()
Using \(0\)-based \(i\) does not change anything, because only equality of \(b\) matters: shifting every \(b\) by \(1\) keeps equal values equal.
Trace on the fourth test, 1 6 3 4 5 6:
| \(i\) | \(a_i\) | \(b = a_i - i\) | earlier with the same \(b\) | ans |
|---|---|---|---|---|
| 0 | 1 | 1 | 0 | 0 |
| 1 | 6 | 5 | 0 | 0 |
| 2 | 3 | 1 | 1 | 1 |
| 3 | 4 | 1 | 2 | 3 |
| 4 | 5 | 1 | 3 | 6 |
| 5 | 6 | 1 | 4 | 10 |
Five indices share \(b = 1\), and \(\binom{5}{2} = 10\) pairs. ✓
Cost: one pass with \(O(1)\) dictionary operations: \(O(n)\) per test, \(O(\sum n)\) overall.
4. Simulation and brute force¶
When the constraints are small, the straightforward solution is the right one. Do exactly what the statement says (simulation), or try every possibility (brute force / enumeration). Just count the steps first:
| situation | direct approach | steps | OK? |
|---|---|---|---|
| 1742C · Stripes: an \(8 \times 8\) grid | check every row and column | \(64\) | yes |
| check all pairs \(i < j\), \(n \le 5000\) | two loops | \(1.25 \cdot 10^7\) | borderline in CPython, fine in PyPy |
| check all pairs \(i < j\), \(n \le 2 \cdot 10^5\) (Same Differences) | two loops | \(2 \cdot 10^{10}\) | no, needs the rewrite |
5. Common mistakes¶
Ignoring the constraints
Coding the first idea without checking \(n\) is the most common reason for TLE. Estimate first, then code.
Hidden \(O(n)\) inside a loop
if x in my_list, my_list.pop(0), a.count(x), sum(a[:i]) inside a loop of \(n\) iterations is \(O(n^2)\). Use a set, a dict, a deque, or a prefix sum.
Allocating per test case with a fixed maximum size
With \(t = 10^4\) tests, cnt = [0] * 200001 inside solve() costs \(10^4 \cdot 2 \cdot 10^5\) even though \(\sum n\) is small. Size arrays by the current \(n\), or use a dict, as above.
Counting only the loop, not the work inside
A loop of \(n\) iterations that calls sorted() each time is \(O(n^2 \log n)\), not \(O(n)\).
6. Practice¶
| Problem | Set | Rating | Point |
|---|---|---|---|
| 1520D · Same Differences | 1 · C, 4-Misc · J | 1200 | this page |
| 1742C · Stripes | 4-Misc · I | 900 | tiny input: simulate |
| 1619C · Wrong Addition | 4-Misc · D | 1200 | simulate digit by digit |
| 1520C · Not Adjacent Matrix | 4-Misc · H | 1000 | \(n \le 100\): construct directly |
| 1807D · Odd Queries | 1 · A, 4-Misc · A | 900 | \(O(nq)\) is too slow, so use a prefix sum |
Credits & licenses
- Section structure follows USACO Guide — Time Complexity by Darren Yao and Benjamin Qi, licensed CC BY-NC-SA 4.0.
- Everything else (tables, Python operation costs, the Same Differences analysis and solution, mistakes) is ours.