graphs dp ← termit.dev

Hard or easy? P vs NP for interviews

In an interview, an NP-hard problem never means “find a polynomial algorithm”. It means: find the parameter that is small, and exploit it. That takes two skills. The first is telling a hard problem from an easy one that looks almost the same — one changed word often moves a problem across the line. The second is reading the constraints: n ≤ 20, n ≤ 40, values up to 104 each point at a specific technique. This page covers both, with C++ for every technique, tested against brute force. The graph algorithms it leans on have their own page, graph algorithms, step by step, and so do the everyday DPs: dynamic programming, step by step.

P, NP, NP-complete, NP-hard

Four words, and only one fact that matters at the whiteboard: for NP-hard problems nobody knows an algorithm that is polynomial in the input size, and finding one would be the most famous result in computer science.

P
Problems with a polynomial-time algorithm: O(n), O(n log n), O(n3)… Sorting, shortest paths, spanning trees, matching. In practice: solvable for big inputs.
NP
Yes/no problems where a “yes” can be checked quickly, given the evidence. “Is there a tour shorter than 100 km?” — show me the tour, I add it up. NP stands for nondeterministic polynomial, not “not polynomial”: every problem in P is in NP too.
NP-complete
The hardest problems in NP: every NP problem can be translated into each of them in polynomial time, so a fast algorithm for one would be a fast algorithm for all. SAT, Hamiltonian path, 3-coloring, subset sum.
NP-hard
At least as hard as the NP-complete problems, without having to be in NP — or even to be a yes/no question. “Find the shortest tour” is NP-hard; “is there a tour of length ≤ K?” is NP-complete. At the whiteboard the two words mean the same thing: don't hunt for a polynomial algorithm.

Whether P = NP has been open since Stephen Cook's 1971 paper, and a proof either way is worth a $1M Clay Millennium Prize. Almost everyone believes P ≠ NP, and so does this page.

reductions point one way

To show your problem X is hard, translate a known hard problem into X: “if I could solve X fast, I could solve Hamiltonian path fast.” The other direction proves nothing. Translating X into SAT only shows X is no harder than SAT — sorting can be written as SAT too.

How the classes fit together NP is a large region containing P. NP-hard is a region that overlaps the bottom of NP; the overlap is NP-complete. NP-hard also extends outside NP. P sorting · shortest paths spanning trees · matching NP answers checkable fast NP-complete SAT · 3-coloring subset sum · Ham. path NP-hard find the shortest tour the halting problem

Assuming P ≠ NP. NP-complete is where NP and NP-hard overlap. If P = NP, the three regions inside NP merge into one.

Spotting a hard problem

Interview problems never say NP-hard. They tell a story — teams, cookies, stickers, delivery routes — around one of six shapes. Strip the story, find the shape, then read the constraints for the way out.

Visit everything exactly once

sounds likevisit all nodes · the shortest string containing every word · walk over every empty cell once · order items so that every neighbor pair fits · the longest path that repeats nothing

underneathHamiltonian path, traveling salesman, longest simple path

way outn ≤ 20 → bitmask DP over (visited set, last) · small grids → backtracking

Cover everything with the fewest picks

sounds likethe smallest team that has every skill · the fewest stickers that spell a word · the fewest cameras, guards or stations that watch everything

underneathset cover, vertex cover, dominating set

way outfew things to cover (≤ 16 skills) → bitmask DP over what is covered · a tree → tree DP · huge → greedy, ln n

Pick things that don't conflict

sounds likeseat students so nobody can copy · rob houses that aren't neighbors · the biggest group where everyone knows everyone · exam slots so nobody has two at once

underneathindependent set, clique, graph coloring

way outnarrow grid → row-by-row bitmask DP · a tree → tree DP · intervals → greedy

Satisfy every constraint

sounds likefill in the Sudoku · place N queens · assign values so every rule holds

underneathSAT, exact cover, constraint satisfaction

way outbacktracking: fill the most constrained spot first, check every rule before recursing. A 9×9 board is small; the general n2×n2 Sudoku is NP-complete.

The 20-second test. Before you say NP-hard, write down the state a DP would need and count how many values it can take. Positions, prefixes, pairs (i, j), a node plus a flag: polynomially many, so the problem is probably in P. “Which items have I used so far”: 2n values — hard territory, and the hunt for a small parameter begins.

Twins: one word moves the line

Most hard problems have an easy twin: edge instead of node, two instead of three, contiguous instead of any. Calling an easy problem NP-hard looks worse in an interview than the reverse, so it pays to know the pairs. Test yourself, then check the table.

pick one; press P or H on the keyboard
polynomialNP-hardwhat changes
Use every edge exactly onceEuler path · step by stepVisit every node exactly onceHamiltonian pathDegrees, plus one connectivity check, settle Euler. Whether a partial Hamiltonian path can still be finished depends on the whole set of nodes it has used.
Connect every city as cheaply as possibleminimum spanning tree · step by stepVisit every city and come back, as cheaply as possibletraveling salesmanChoosing edges versus choosing an order. The cut property lets greedy commit to an edge; nothing lets you commit to one leg of a tour.
Connect every cityminimum spanning treeConnect only the marked cities, through any of the othersSteiner treeWhich unmarked cities to route through is a subset choice.
Color the nodes with 2 colors so no edge joins two of the samebipartite check · BFSThe same with 3 colors3-coloringWith two colors every choice is forced by a neighbor. With three, choices stay open and interact across the graph.
Shortest path between two nodes, no negative cyclesDijkstra, Bellman–FordLongest path that repeats no nodelongest simple pathA shortest path never wants to revisit a node, so “no repeats” comes free. A longest one always would, so it becomes a global constraint.
Longest path in a graph with no cyclesDAG · topological order + DPLongest path in a graph with cycleslongest simple pathThe topological order gives the DP an order to fill in. Cycles take it away.
Pair workers with jobs they can do, as many pairs as possiblebipartite matchingGroup workers, jobs and machines into compatible triples3-dimensional matchingAn augmenting path improves a matching one pair at a time. Triples have no such move.
Fewest edges that touch every nodeedge coverFewest nodes that touch every edgevertex coverEdge cover = n − maximum matching. Vertex cover has no such formula, except in bipartite graphs, where König's theorem makes it a matching too.
Most intervals with no two overlappinginterval schedulingMost nodes with no edge between any twomaximum independent setOn a line, the interval that ends first is always a safe pick. A general graph has no such order.
Fewest rooms for a set of meetingscoloring intervalsFewest colors for any graphchromatic numberFor intervals the answer is the largest overlap at a single moment, found by one sweep.
Split an array into two contiguous parts with equal sumsprefix sumsSplit it into two subsets with equal sumspartitionContiguous: n − 1 places to cut. Subsets: 2n ways to choose.
Fill a bag when items can be cut into piecesfractional knapsackFill a bag with whole items0/1 knapsackCutting lets greedy go by value per weight. Whole items need a DP over capacity, which is only pseudo-polynomial.
Unit-time jobs with deadlines and profits, most profitgreedy + union-findJobs of any length with deadlines and weights, least weight lateweighted late jobsEqual lengths make jobs interchangeable, so greedy by profit is safe (a matroid). Different lengths turn it into knapsack.
Satisfy clauses of two literals each, like (a or not b)2-SATSatisfy clauses of three literals each3-SATA two-literal clause is two implications; strongly connected components of the implication graph decide it.
Drive down every street, as short as possibleChinese postmanStop at every house, as short as possibletraveling salesmanStreets are edges again: Euler's degree test, plus a matching that pairs up the odd-degree corners.
Optimize a linear objective over real numberslinear programmingThe same, with whole numbers onlyinteger linear programmingReal solutions form one convex region, where a local optimum is global. Requiring integers breaks it into separate points.

same cities, two questions

Drag the cities. On the left, the cheapest network that connects them all: Kruskal and Prim decide one edge at a time, on its own merits, so this scales to millions of cities. On the right, the cheapest round trip, found with the 2n·n2 DP from the playbook — which is why this demo stops at 12 cities. The dashed tour walks around the tree and skips cities it has seen. It is never longer than twice the tree, and the tree is never longer than the best tour (remove one leg of a tour and you have a spanning tree), so the walk is within 2× of optimal.

drag a city, or focus it and use the arrow keys
connect them allspanning tree
visit them all, come backshortest tour

At 60 cities, Kruskal sorts 1,770 edges. There are 6.9 × 1079 different round trips, and even the DP needs 4.2 × 1021 steps.

Why the easy twins are easy

Every polynomial problem in the table escapes through one of four hatches. Before calling a problem hard, look for each of them. If none is there, it probably is hard.

An order to fill the DP in

A DP needs each subproblem finished before it is used. A topological order provides that in a DAG; so do children-before-parents in a tree, left-to-right along a timeline, and positions in a string. Cycles take the order away, which is exactly why longest path is easy in a DAG and hard in general.

LC 329 Longest Increasing Path in a Matrix · 337 House Robber III · 139 Word Break · 1235 Maximum Profit in Job Scheduling

No real choice

When every decision is forced by an earlier one, there is nothing to search. With two colors, one node's color fixes its neighbors', and theirs, all the way out: a BFS. In 2-SAT every clause is a pair of implications. With three colors, the choices stay open, and they interact.

LC 785 Is Graph Bipartite? · 886 Possible Bipartition

A swap argument lets greedy commit

If any optimal answer can be rearranged to include the greedy choice without getting worse, greedy is safe. That is the cut property for spanning trees (Kruskal, Prim), closest-first for Dijkstra with weights ≥ 0, and earliest-ending interval first. Try the same swap on TSP or 0/1 knapsack and it fails — and so does greedy.

LC 435 Non-overlapping Intervals · 1584 Min Cost to Connect All Points · 630 Course Schedule III

Augmenting paths

Matching and flow improve one step at a time: while an improving path exists, take it; when none is left, the answer is optimal (Berge's theorem for matchings, max-flow = min-cut for flows). That covers pairing workers with jobs, minimum cuts, and edge cover. Triples have no improving paths.

LC 1820 Maximum Number of Accepted Invitations · 1066 Campus Bikes II (small n: bitmask DP is expected)

The hard side has none of the four. You are choosing a subset or an order, and every choice constrains every other one.

The wall: why n tells you the technique

A judge runs roughly 108 simple C++ operations a second. Polynomial curves stay far under that line for any n you will be given; exponential ones hit it at a definite n — and that n is the constraint printed in the problem. n ≤ 20 means 2n is expected. n ≤ 40 means 2n/2, done twice: meet in the middle.

    The largest n that fits in about a second
    growthtypical usemax n in 1 s
    n log nsorting, heaps, Dijkstra≈ 4.5 million
    n2DP over pairs (i, j)10,000
    n3Floyd–Warshall, interval DP≈ 460
    2n/2·nmeet in the middle≈ 42
    2nevery subset≈ 26
    2n·nbitmask DP, one item per step≈ 22
    2n·n2bitmask DP with a last node: TSP≈ 18
    3nDP over submasks≈ 16
    n!every order≈ 11

    108 a second is a rule of thumb, good to a factor of 3 or so: tight loops over arrays and bits run faster, hash maps and allocations slower. Setters leave room, so expect n ≤ 20 for 2n·n and n ≤ 12–16 for 2n·n2 or 3n.

    The playbook: let the constraints pick

    Once you have said “this is NP-hard”, the next sentence names the small parameter. Ask for the constraints before anything else: they are the setter's hint.

    what is smallreach forcost
    n ≤ 10 or 11try every order: next_permutationO(n!·n)
    n ≤ 20bitmask DP over subsetsO(2n·n) to O(2n·n2)
    n ≤ 15, split into groupsDP over submasksO(k·3n)
    n ≤ 40meet in the middleO(2n/2·n)
    the values: sums up to ~106DP over sumsO(n·S)
    the answer kbranch on a forced choiceO(2k·E)
    nothing, but any valid answer will dobacktracking that prunesexponential, often fast
    the structure: tree, DAG, intervals, bipartitethe special case's algorithmpolynomial
    nothing, and n is hugean approximation with a known ratiopolynomial

    n ≤ 20: bitmask DP

    Brute force tries all n! orders. But to extend a partial tour you only need to know which cities it has visited and where it stands now — not the order it took. So the state is (mask, last): 2n·n states, n ways out of each, O(2n·n2) in total. At n = 15 that is 7.4·106 steps instead of 1.3·1012.

    • mask >> i & 1 asks whether i is in the set, mask | 1 << i adds it, __builtin_popcount(mask) counts it.
    • Go through the masks in increasing order: adding an element makes the number bigger, so every state a mask is built from is already done.
    • Memory is 2n·n entries: 21 million at n = 20, 168 MB as long long. Use int when the distances allow.
    • When the set's size says where you are, drop “last”: matching n items to n slots (LC 1879, 1066) needs only dp[mask], O(2n·n).
    • With unit-length edges, the same state in a BFS queue gives LC 847: BFS over (node, mask).

    practice

    Groups: DP over submasks

    When the answer splits the items into groups — workers, sessions, children — let dp[mask] be the best answer for exactly the items in mask, and let the newest group take any submask of it. The loop for (sub = mask; sub; sub = (sub - 1) & mask) visits every nonempty submask exactly once.

    Summed over all masks that is 3n, not 4n: each item is either in sub, in mask but not in sub, or outside mask. With k groups the program below costs O(k·3n), fine up to n ≈ 15 (315 = 1.4·107).

    Precompute what each group costs (load[mask]) once, in O(2n), by peeling off the lowest set bit.

    practice

    n ≤ 40: meet in the middle

    240 ≈ 1012 subsets is far too many; 2 × 220 ≈ 2·106 is not. Split the items into two halves and list every subset sum of each. A subset of everything is a subset of the left half plus a subset of the right, so sort one list, and for each sum x in the other, binary-search the partner closest to goal − x. O(2n/2·n) time, O(2n/2) memory.

    It works whenever an answer splits into two independent halves that combine through a sort or a hash map: sums, XORs, counts of ways.

    practice

    Small values: DP over sums

    Subset sum is NP-complete, yet O(n·S) solves it, where S is the target. No contradiction: S is a number written in about log2 S bits, so n·S is exponential in the length of the input. Algorithms like this are called pseudo-polynomial, and only weakly NP-hard problems have them — the knapsack family. TSP, bin packing and 3-partition are strongly NP-hard: no DP over numbers helps. Say this sentence in the interview; it is the difference between “I memorized a DP” and “I know why it works”. The same table, filled in step by step: 0/1 knapsack and coin change.

    With a bitset each item costs one shift and one OR over S bits. Step through it:

    try:

    • reachable
    • new this step
    • target

    • The 0/1 knapsack loop runs capacity downward, so each item is used at most once. Upward allows unlimited copies: that is coin change (LC 322).
    • Two budgets, like the counts of 0s and 1s in LC 474, give dp[i][j]. Still pseudo-polynomial.
    • Negative numbers (LC 494): shift every sum by the total so the indexes stay ≥ 0.

    practice

    Small answer: branch on what is forced

    If the question is whether at most k picks suffice, and k is small, look for something that forces a choice. Every uncovered edge needs one of its two ends in a vertex cover, so branch on both: the search tree is at most k deep with at most 2k leaves.

    O(2k·E) is exponential in k only: k = 10 with 100,000 edges is about 108 steps, while trying every set of 10 nodes out of 10,000 would take about 1033. Problems like this are called fixed-parameter tractable. Interviews rarely ask for one by name, but the idea — put the exponential cost on the parameter that is small — is exactly what the interviewer is listening for.

    Nothing small: backtracking that prunes

    When no parameter is small but any valid answer will do, search — and spend the effort on cutting branches early. The grade is in the pruning:

    • Most constrained first. Big numbers first; the Sudoku cell with the fewest options first. Dead ends then show up near the root, where cutting them saves the most.
    • Check before recursing. Remaining capacity, whether the remaining sum can still reach the target.
    • Break symmetry. Equal values at the same depth, or two groups with the same load, lead to mirror-image searches. Try one of them.
    • Branch and bound when optimizing. Keep the best answer so far and an optimistic bound for the branch (fractional knapsack makes a good one); cut when the bound can't beat the best.
    • Memoize when states repeat. A set of failed masks turns this search into the bitmask DP above. Saying so out loud is a good moment in an interview.

    practice

    Structure: the polynomial special case

    Many NP-hard graph problems are easy on special graphs, so check whether the input really is a general graph. Independent set, vertex cover and dominating set are linear on trees: finish the children before the parent and keep two answers per node, with it and without it.

    Likewise longest path is linear in a DAG (topological order), coloring intervals takes one sweep, and in a bipartite graph the smallest vertex cover is as big as the largest matching.

    practice

    n is huge: approximate, and say the ratio

    With n = 105 and no structure, exact is off the table. Say so, then offer an algorithm with a proven ratio. Knowing a few by heart is a senior signal:

    • Vertex cover: both ends of a maximal matching — at most 2× optimal.
    • Set cover: greedy, most new elements first — at most H(n) ≈ ln n times optimal.
    • TSP with the triangle inequality: walk around the spanning tree — at most 2× (the demo above); Christofides — 1.5×.
    • Bin packing: first-fit decreasing — about 11/9 of optimal.
    • Makespan on m machines: longest job first, to the least loaded machine — at most 4/3×.
    • 0/1 knapsack: the better of greedy by value per weight and the best single item — at least half of optimal; scaling the values down gets within any 1 + ε.

    No guarantee needed? Local search, simulated annealing and random restarts are the practical fallback.

    In the room

    The order to say things in, so the interviewer hears the reasoning and not only the code.

    1. Ask for every size: n, the range of the values, exact or approximate, one answer or all of them or how many.
    2. Name the hardness in one sentence, then look for a way out. “This is subset sum in disguise, so I won't look for a polynomial exact algorithm — unless the values are small.”
    3. Point at the small parameter, pick the technique, and say the complexity before writing code: “2n·n2 with n = 16 is about 1.7·107.”
    4. Write it in the order brute force → memoize → prune. A correct 2n beats a broken clever greedy.
    5. Close with the cliff: “If n grew to 105, I'd switch to a greedy with a known ratio, or local search.” It shows you know where the approach stops working.

    LC 1723, k workers, n ≤ 12 jobs — out loud:

    “Splitting jobs between workers to minimize the busiest one is makespan scheduling, which is NP-hard, so I won't look for a polynomial exact solution.

    n is at most 12, so a set of jobs fits in a bitmask. I'll precompute the total time of every subset, then go worker by worker: dp[mask] is the best finishing time when the workers so far have exactly the jobs in mask, and the next worker takes some submask. That's O(k·3n), about 6·106 for n = 12.

    If n were 105, I'd sort the jobs from longest to shortest and give each to the least loaded worker: within 4/3 of optimal.”

    Pitfalls

    • Greedy on a 0/1 problem. Numbers {6, 5, 5}, target 10: largest first takes 6 and is stuck, while 5 + 5 works. Keep a counterexample ready, because the interviewer will have one.
    • O(n!) where O(2n·n) was available. The order inside the visited set doesn't matter, only the set and the last element. At n = 15: 1.3·1012 against 4.9·105.
    • Calling a knapsack DP polynomial. O(n·S) is pseudo-polynomial: say why, and that it only exists because the values are small.
    • Apologizing for exponential output. “Return all solutions” can't run faster than the size of the answer. Counting them, though, usually has a DP.
    • Reducing the wrong way. Turning your problem into SAT proves nothing. To show it is hard, turn a known hard problem into yours.
    • Calling an easy problem hard. Longest path in a DAG, 2-coloring, matching, meeting rooms and word break are all polynomial. Spend 20 seconds on the twins before saying NP-hard.