Search Tech Journey

Find topics, journeys and posts

back to blog
algorithmsbeginner 60m read

L01 · The LeetCode Game — How to Read a Problem

Constraints are not decoration. They are the interviewer quietly telling you which complexity class is acceptable, and therefore which algorithm to reach for.

🧩DSAPhase 0 · Foundations· Session 001 of 130 60 min

🎯 Learn to read the constraints block first and convert it into a shortlist of acceptable complexity classes — before you even understand what the problem is asking.

Series: LeetCode — From Basics to Interview-Ready · Session 1 / 65 · Phase 0 · Foundations

What this session IS — reading the tiny print

Every LeetCode problem has three parts: a story, some examples, and — at the very bottom, in a block most people scroll past — the Constraints. It looks like this:

Constraints:
  1 <= n <= 10^5
  -10^9 <= nums[i] <= 10^9

Beginners read the story and start coding. Strong candidates read the constraints first. Here is why: the constraint on n is the problem setter quietly telling you which algorithms are allowed to pass, and therefore which one they intend you to find.

A computer does roughly 10^8 (a hundred million) simple operations per second in a judge's time limit. That single fact turns the size of n into a budget. If n is 100,000 and your algorithm does n^2 work, that is 10^10 operations — a hundred times over budget. It will time out. The constraint just told you: do not write two nested loops over n.

The analogy
🌍 Real world
A speed-limit sign does not tell you where you are going. It tells you how fast you are allowed to travel to get there. Read it before you pick the route.
💻 Code world
The constraint n <= 10^5 does not tell you the algorithm. It tells you the algorithm must be about n log n or faster — which prunes the choices down to a handful before you have understood the problem at all.

The naive approach and why it is slow

Take "find two numbers in a list that add up to a target". The obvious approach: try every pair.

def two_sum_brute(nums, target):
    for i in range(len(nums)):
        for j in range(i + 1, len(nums)):
            if nums[i] + nums[j] == target:
                return [i, j]
    return []

Two nested loops over n items is n^2 pairs. If the constraints say n <= 10^4, that is 10^8 — right at the edge, might pass. If they say n <= 10^5, that is 10^10 — dead. The same code is fine or fatal depending only on a number at the bottom of the page. That is why you read it first.

The table you must memorise

This is the whole session compressed into one lookup. Given the largest input size, here is the fastest complexity you are allowed to need:

Largest nRough op budgetAcceptable complexityTypical technique
n ≤ 12tinyO(n!)permutations, brute backtracking
n ≤ 20tinyO(2^n)subsets, bitmask DP
n ≤ 500~10^8O(n^3)interval DP, Floyd–Warshall
n ≤ 5,000~10^7O(n^2)pairwise DP, quadratic scans
n ≤ 10^5~10^8O(n log n)sort, heap, binary search, two pointers
n ≤ 10^6~10^8O(n)single pass, hashing, sliding window
n ≥ 10^9hugeO(log n) or O(1)binary search on the answer, math

Read the row that matches the biggest number in the constraints. Everything to the right of your budget times out. Everything to the left is allowed but usually overkill.

From first principles
Start with the question
Why can a single number in the constraints tell me the algorithm before I understand the problem?
  1. 1
    A judge allows roughly 10^8 primitive operations in its time limit.
    forced by · That is about one second of CPU work, and time limits are set around that.
  2. 2
    Your algorithm's running time is (operations per element) times (number of elements), expressed as a Big-O in n.
    forced by · Big-O counts how the work grows as n grows.
  3. 3
    If you plug the largest allowed n into your Big-O and exceed 10^8, the code times out.
    forced by · You have asked for more work than the budget allows.
  4. 4
    So the constraint on n rules out every complexity class above a threshold.
    forced by · Only the classes whose value at that n stays under 10^8 can pass.
  5. 5
    Each surviving complexity class maps to a small family of techniques.
    forced by · O(n log n) means sort/heap/binary-search; O(n) means one pass/hashing; O(log n) means binary search on the answer.
⇒ Therefore
The largest n in the constraints selects a complexity budget, and the budget selects a short list of techniques — all before you have solved anything.

The template — a reading ritual

There is no code skeleton this session; the "template" is a fixed sequence you run on every problem before writing a line. Say it out loud.

1. Jump to Constraints. Note the LARGEST n.2. Look up the budget row say the target complexity out loud. "n is 10^5, so I need about n log n; n^2 will time out."3. Note the VALUE range. Negatives? Duplicates? Sorted? These flip which technique works (e.g. negatives break sliding window).4. Read the actual question. Now the technique family is already narrowed.5. State brute force + its cost. Then improve toward the budget.

The single most valuable sentence you can utter in an interview is step 2, said before you code:

"n is up to ten to the fifth, so I'm targeting n log n — checking all pairs is out."

It signals you cost things before writing them, which is most of what the interviewer is scoring.

Mental modelThe budget line
A horizontal number line of complexities: O(1) — O(log n) — O(n) — O(n log n) — O(n^2) — O(2^n) — O(n!). The constraint on n drops a vertical fence somewhere on that line. Everything to the right of the fence times out.
  • Bigger n pushes the fence LEFT (fewer techniques allowed).
  • n <= 20 pushes it far right — exponential is invited, look for subsets/backtracking.
  • n = 10^5 or 10^6 pushes it to n log n / n — think sort, hash, two pointers, sliding window.
  • Always read the fence position BEFORE reading the story.
🔔 Fires when you see
Any time you open a problem: your first eye movement is to the Constraints block, not the title.

Memory technique — the powers-of-ten peg

You do not need to memorise the whole table if you memorise three anchor pegs and interpolate:

  • "Twenty is exponential." n ≤ 20 → 2^n / n! are fine. Peg image: a tiny room where you can afford to try every arrangement.
  • "Hundred-thousand is log-linear." n ≤ 10^5 → n log n. Peg image: a sorted filing cabinet — you sort once, then everything is fast.
  • "Million is one pass." n ≤ 10^6 → O(n). Peg image: a conveyor belt you may touch each item exactly once.

Chant it as a rhythm: "twenty exponential, hundred-K log-linear, million one-pass." Three pegs, said as one phrase, recovers the whole table because everything else sits between them.

Common misconception
✗ What most people think
I should read the whole problem carefully first, then worry about efficiency once I have a working solution.
✓ What is actually true
Read the constraints first. They determine which working solutions are even acceptable, so they shape the approach from the start.
Why the myth is so sticky
A correct O(n^2) solution that times out scores lower than a stated plan for the O(n) one. Reading constraints last means you often design the wrong algorithm and have to restart with the clock running.
Prove it to yourself
Open any Medium problem. Cover the story. From the constraints alone, can you name the target complexity and one likely technique? If yes, you are reading correctly.

What interviewers actually ask

Reading constraints is not a problem type — it is the habit that sits under every problem. But interviewers deliberately encode signals in the numbers, and these classic problems reward reading them:

  • Two Sum (1) · Amazon, Google, Meta (Easy) — probes: do you notice n is large enough that the O(n^2) pair scan is a warm-up, not the answer. Follow-up: "what if the array were sorted?" (drops you into two-pointers, L07).
  • Best Time to Buy and Sell Stock (121) · Amazon, Microsoft (Easy) — probes: n <= 10^5 forbids the O(n^2) "try every buy/sell pair"; the constraint is the hint to find the one-pass running-min. Follow-up: "with multiple transactions?"
  • Search in Rotated Sorted Array (33) · Amazon, Microsoft, Meta (Medium) — probes: the problem says O(log n) is expected; that single line forces binary search over a linear scan. Follow-up: "with duplicates" (81).
  • Subsets (78) · Amazon, Meta (Medium) — probes: n <= 10 (tiny) is the setter inviting an exponential 2^n enumeration; reading it tells you backtracking is intended, not feared. Follow-up: "with duplicates" (90).
  • Koko Eating Bananas (875) · Amazon, Meta (Medium) — probes: huge value range for the answer with small n signals binary search on the answer, not on the array. Follow-up: "minimise something else under a budget."
  • Median of Two Sorted Arrays (4) · Google, Amazon (Hard) — probes: the problem states O(log(m+n)); that constraint alone rules out merging and forces a partition binary search.

Worked example 1 — reading n to pick the tool

Problem in plain words: Given a list nums and a target, return the indices of two numbers that add to target. Constraints: 2 <= n <= 10^4, values fit in normal ints.

Brute force + cost. Try every pair (the code above). Cost: O(n^2). At n = 10^4 that is 10^8 — borderline, probably passes, but it is the warm-up, and you should say so.

The insight. The budget row for 10^4 allows O(n^2), but the value range and "find a complement" phrasing invite O(n) with a hash map: for each number, ask whether target - number was already seen.

def two_sum(nums, target):
    seen = {}                      # value -> index we saw it at
    for i, x in enumerate(nums):   # one pass, O(n)
        need = target - x          # the complement that would complete the pair
        if need in seen:           # dict lookup is O(1) average
            return [seen[need], i]
        seen[x] = i                # remember x for a future partner
    return []

Complexity. Time O(n) (one pass, O(1) lookups). Space O(n) for the map. We spent memory to buy time — the core trade of L05.

Follow-up they escalate to. "What if the array were already sorted — can you do it in O(1) extra space?" That drops the map for two converging pointers, which is exactly L07.

Try itProve to yourself the budget matters.
import random, time
nums = [random.randint(-10**9, 10**9) for _ in range(20000)]
target = nums[3] + nums[19999]   # a real pair near the ends
 
t = time.time(); two_sum_brute(nums, target); print("brute", round(time.time()-t, 3), "s")
t = time.time(); two_sum(nums, target);       print("hash ", round(time.time()-t, 3), "s")

You should see the brute version an order of magnitude slower — that gap is the difference between "passes" and "time limit exceeded" as n grows.

💡 Hint · Time both on a big random list.

Worked example 2 — a tiny n inviting exponential work

Problem in plain words: Return every subset of a list of up to 10 distinct numbers. Constraints: n <= 10.

Reading first. n <= 10 is a tiny number. From the table, that is deep in exponential territory: 2^n = 1024 subsets, trivial. The constraint is the setter saying "enumerate everything, do not be clever." Fearing the exponential here is the mistake.

def subsets(nums):
    result = [[]]                  # start with the empty subset
    for x in nums:                 # for each number...
        # ...double the collection: every existing subset, plus a copy that includes x
        result += [subset + [x] for subset in result]
    return result
 
# subsets([1,2,3]) -> [[], [1], [2], [1,2], [3], [1,3], [2,3], [1,2,3]]

Complexity. There are 2^n subsets and we build each once: O(n · 2^n) time and output. At n = 10 that is about 10,000 — instant. The constraint guaranteed this would be fine before we wrote it.

Follow-up. "What if the list has duplicates and you must not repeat subsets?" → sort first, skip a value when it equals the previous one at the same depth (Subsets II, 90).

Worked example 3 — the answer's range, not n, sets the budget

Problem in plain words: Koko eats bananas from piles; she eats k bananas/hour from one pile. Find the smallest k that finishes all piles within h hours. Constraints: n <= 10^4 piles, each pile up to 10^9 bananas.

Reading first. n is small (10^4) but a pile can be 10^9. A linear scan of possible speeds k from 1 upward would be up to 10^9 tries — far over budget. The answer's range is huge and the "smallest k that satisfies a condition" phrasing is the tell: binary search on the answer.

def min_eating_speed(piles, h):
    import math
    def hours_at(speed):                       # hours needed if she eats `speed`/hr
        return sum(math.ceil(p / speed) for p in piles)
 
    lo, hi = 1, max(piles)                      # speed is between 1 and the biggest pile
    while lo < hi:                              # binary search for the smallest feasible speed
        mid = (lo + hi) // 2
        if hours_at(mid) <= h:                  # mid is fast enough → try slower (fewer bananas/hr)
            hi = mid
        else:                                   # too slow → must eat faster
            lo = mid + 1
    return lo                                    # lo == hi == smallest feasible speed

Complexity. O(n log(max pile)) — the log turns 10^9 candidate speeds into ~30 checks, each O(n). Comfortably under budget. Reading the value range, not just n, is what pointed here.

Follow-up. "Ship packages within D days (1011)" and "Split array largest sum (410)" are the same binary-search-on-answer shape.

The tradeoff
Should I optimise for the constraint's budget, or just write the clearest brute force?
Match the budget exactly
+ you gain Passes the judge; signals cost-awareness to the interviewer.
− you pay More thinking up front before any code.
pick when Interviews and any n at the top of its table row.
Write brute force first, optimise if needed
+ you gain Fast to a correct answer; good for verifying with tests.
− you pay Times out on large n; wastes clock if you have to rewrite.
pick when Small constraints (n <= a few hundred) or as a stated stepping stone.
Over-optimise beyond the budget
+ you gain Feels impressive.
− you pay Extra bugs and time for speed the constraints never asked for.
pick when Almost never — hit the budget, then stop.
What a senior engineer actually does
Read the budget, state it, then aim for the simplest algorithm that fits it. Brute force is a fine opening move only when you say why and when you will replace it.

Where this shows up in production

The constraint-reading instinct is the same one senior engineers use when sizing a system: before choosing a data structure they ask "how big does this get?" A lookup over a few dozen config keys can be a plain list scan; the same scan over millions of rows per request needs an index or a hash map. The habit — let the size choose the structure — is identical to reading n before choosing an algorithm. That is why interviewers value it: it is a small, honest proxy for how you will make real design decisions.

You can now…
  • Find the Constraints block first and read off the largest n.
  • Recite the powers-of-ten pegs: twenty exponential, hundred-K log-linear, million one-pass.
  • Convert a value of n into a target complexity out loud before coding.
  • Spot when the ANSWER's range (not n) sets the budget → binary search on the answer.
  • State a brute force and its cost, then justify moving toward the budget.
  • Explain why a correct O(n^2) can score worse than a planned O(n).
Recall check · click to reveal
★ = stretch question

Practice queue

Grind these in order. For each, cover the story and predict the target complexity from constraints before reading the ask.