Reading Constraints to Choose an Approach

beginner25 min

The practical version

Big O notation gets taught as theory. In a contest it is a decision tool: you read the maximum input size, and it tells you which approaches can possibly pass. Getting this right before writing code is the difference between four hours well spent and four hours spent optimizing something that was never going to work.

The judge gives you a time limit, typically two seconds for Java. A rough working estimate is that you can perform around 108 simple operations per second. This is imprecise — the real number depends on what the operations are — but it is accurate enough to choose an approach.

The table to memorize

What fits in the time limit

Max NAffordable complexityTypical technique
N ≤ 10O(N!)Try every permutation
N ≤ 20O(2N)Try every subset, bitmask DP
N ≤ 500O(N3)Floyd–Warshall, some interval DP
N ≤ 5,000O(N2)All pairs, simple DP
N ≤ 200,000O(N log N)Sorting, binary search, segment tree, sets
N ≤ 106O(N)Single pass, prefix sums, two pointers
N ≤ 1018O(log N) or O(1)Math, binary search on the answer, matrix power

This table is the most useful thing in this module. The input size is not decoration in the problem statement — it is the problem telling you what it wants.

How to use it

Read the constraints first, before you finish reading the statement.

If N ≤ 5,000, a nested loop over all pairs is fine and you should stop looking for something cleverer. If N ≤ 200,000, a nested loop is 4 × 1010 operations and will not finish — you need sorting, a set, or a prefix sum.

Counting operations

To find the complexity, count how many times the innermost work runs as a function of the input size.

// O(N) — the loop body runs n times
long sum = 0;
for (int i = 0; i < n; i++) {
    sum += a[i];
}
// O(N^2) — for each i, the inner loop runs up to n times
int best = 0;
for (int i = 0; i < n; i++) {
    for (int j = i + 1; j < n; j++) {
        best = Math.max(best, a[i] + a[j]);
    }
}

The second example runs about N2/2 times, not N2. Big O drops the constant factor, so both are O(N2). That is usually the right call, but not always — see the caveat below.

// O(N log N) — sorting dominates the linear scan after it
Arrays.sort(a);
for (int i = 0; i < n; i++) {
    // constant work
}

Where the log comes from

A log N factor appears when something repeatedly halves the search space or when you use a balanced tree structure.

Common operations and their cost

OperationCost
Arrays.sort on primitivesO(N log N)
Binary search in a sorted arrayO(log N)
HashMap get or putO(1) average
TreeMap get or putO(log N)
PriorityQueue add or pollO(log N)
ArrayList add at endO(1) amortized
ArrayList remove from middleO(N)
LinkedList access by indexO(N)

For N = 200,000, log N is about 18. So O(N log N) is roughly 3.6 million operations — comfortable. O(N2) is 4 × 1010 — hopeless. That gap is the single most important boundary in intermediate and advanced problems.

Space matters too

Memory limits are usually 256 MB. Java’s overhead is higher than C++’s, so it is worth a quick check.

Rough memory costs

  • int[1_000_000] is about 4 MB. Fine.
  • int[10_000][10_000] is 400 MB. Too much.
  • long[1_000_000] is about 8 MB. Fine.
  • Integer[1_000_000] is roughly 20 MB — boxed objects carry per-object overhead. Prefer primitive arrays.
  • A HashMap with a million entries can approach 100 MB. Prefer an array if the keys are small integers.

The last two points are Java-specific and catch people who are used to C++ memory estimates. When keys are bounded integers, an array is both faster and much smaller than a map.

When constants actually matter

Big O hides constant factors, and occasionally the constant decides the outcome. Two O(N log N) solutions can differ by a factor of ten if one allocates objects in the inner loop and the other does not.

Practical notes for Java

These do not change the complexity, but they change whether you pass:

  • Use primitive arrays (int[]) rather than ArrayList<Integer> in hot loops.
  • Arrays.sort on int[] uses a quicksort variant; on Integer[] or objects it uses merge sort with boxing overhead. The primitive version is substantially faster.
  • Avoid creating objects inside a loop that runs 106 times.
  • Reading input carelessly can cost more than your algorithm. See Reading Input Fast.

There is one genuine correctness caveat to Arrays.sort on primitives: its quicksort can be driven to O(N2) by adversarial input. This is rare in practice but real in some contests. If you are worried, sort boxed Integer[] (merge sort, guaranteed O(N log N)) or shuffle the array before sorting.

Worked example

A problem gives N ≤ 100,000 integers and asks whether any two of them sum to a target X.

The direct approach is to check every pair: O(N2) = 1010. Too slow.

Consulting the table, N ≤ 100,000 allows O(N log N) or O(N). Two approaches fit:

// O(N) average — one pass with a hash set
Set<Integer> seen = new HashSet<>();
boolean found = false;
for (int i = 0; i < n; i++) {
    if (seen.contains(x - a[i])) {
        found = true;
        break;
    }
    seen.add(a[i]);
}
// O(N log N) — sort, then two pointers from both ends
Arrays.sort(a);
int lo = 0, hi = n - 1;
boolean found = false;
while (lo < hi) {
    int sum = a[lo] + a[hi];
    if (sum == x) { found = true; break; }
    else if (sum < x) lo++;
    else hi--;
}

Both pass. The point is that the constraint told you to look for one of them instead of submitting the nested loop.

Choose the approach

For each set of constraints, name the complexity you should target and one technique that achieves it.

  1. N ≤ 18, and you must decide which subset of items to take.
  2. N ≤ 1,000, and you need the shortest path between every pair of nodes.
  3. N ≤ 300,000, and you need the k-th smallest element.
  4. N ≤ 1012, and you need to know whether N is prime.
  5. N ≤ 2,000, and you need the longest common subsequence of two strings of length N.
Answers
  1. O(2N) — 218 is about 262,000. Enumerate subsets with bitmasks.
  2. O(N3) is 109, which is too slow in Java. Run Dijkstra from each node instead: O(N · M log N).
  3. O(N log N) — sort and index, or use a heap of size k.
  4. O(√N) — trial division up to √N is about 106 steps.
  5. O(N2) — 4 × 106 DP states. Standard LCS table.

Related