If you’ve ever studied CS, you’re probably familiar with the infamous P versus NP problem. In this post, I’ll try to explain why we even care about this problem, what it means to be NP-hard and NP-complete, and lastly how to show whether a given problem belongs in these categories or not.

Preliminaries

A basic understanding of models of computation would be helpful!

Why P?

Why should we draw the divide between “efficient” and “inefficient” as between P and NP in the first place? After all, an algorithm that runs in 𝑛100 time is surely worse than one that runs in 2𝑛1000, right? One may make the argument that the former is asymptotically better than the latter, but this never felt super compelling to me.

I think a less subjective argument than “efficient vs inefficient” that justifies our special treatment of the P class is the fact that polynomials are closed under addition and multiplication. This means that even if fundamental operations started to run in 𝑂(𝑛2) rather than 𝑂(1), a polynomial algorithm in the latter circumstance would remain polynomial in the former. In a way, this makes the notion of P machine-invariant, as long as both machines can run fundamental operations in polynomial time.

This invariance means that although P and NP were originally defined relative to the operations required by a Turing machine, we may instead replace the Turing machine—which is rather unwieldy to reason about—with a more “efficient” machine that can perform basic operations like addition, multiplication, and memory access in 𝒪(1) time, while leaving the classes P and NP undisturbed.

What is NP?

Contrary to somewhat popular belief, NP does not mean non-polynomial but rather nondeterministic polynomial, which refers to any program that a nondeterministic Turing machine (as described in models of computation) may run in polynomial time. A nice way of rephrasing this it that a problem is in NP if it can be verified in polynomial time. For instance, consider the classic NP-complete subset sum problem:

Given a list of 𝑛 integers and a target 𝑇, determine whether there exists a subset of the 𝑛 integers that sums to 𝑇.

To solve this, we can write a simple non-deterministic program that runs in 𝒪(𝑛):

  1. Initialize a variable sum to 0.
  2. Every time we read a new number x from the input, split the program into two branches: one where sum is incremented by x, and another where its not.
  3. After processing all 𝑛 numbers in this fashion, our computation tree has 2𝑥 leaves. Check if sum in any of these is equal to T.

Alternatively, if we specify a specific leaf among the 2𝑥 possibilities to evaluate—which basically corresponds to you giving me a subset of numbers and asking me whether they sum to 𝑇—this can be done in 𝒪(𝑛) time. This is what it means to be verifiable in polynomial time.

NP-hard

We call a problem NP-hard if any other problem in NP can be reduced to this one in polynomial time. Consequently, if any NP-hard problem can be solved in polynomial time, we have proven that P = NP. We proceed to offer some classic examples of NP-hard problems and also sketch proofs of their NP-hardness.

Boolean satisfiability (SAT)

This problem is perhaps one of the most famous NP-complete problems, and the Cook-Levin theorem that proved its completeness is a landmark result in CS (for a formal proof, the stanford notes are a great resource).

The SAT problem essentially asks: given an acyclic Boolean circuit composed of AND, OR, and NOT gates that inputs 𝑛 bits and outputs 1 single bit, does there exist a way to set the 𝑛 input bits such that the output bit is 1?

The intuitive reason why this is NP-complete is because if you look inside a computer, it’s also basically just composed of logic gates. Therefore, any verifier program that works in polynomial time can be rewritten as a Boolean circuit in polynomial time. Then, we can run SAT on this circuit to determine whether any input is accepted by the verifier program.

Before we move on, we should probably clarify how exactly to represent SAT problems. The typical form used is conjunctive normal form (CNF) which write the output bit as a conjunction of disjunctions like so:

(𝑥1¬𝑥2𝑥3)(𝑥2¬𝑥3)

denotes logical and, denotes logical or, and ¬ denotes negation. The problem is then to find whether there exists some 𝑥1,𝑥2,𝑥3 such that the above expression evaluates to 1.

The cousin of CNF is disjunctive normal form (DNF) which swaps and . However, we prefer CNF since any boolean formula may be converted into an equisatisfiable CNF formula in polynomial time. The same is not true for DNF; for instance, the DNF expansion of

(𝑥1𝑥2)(𝑥3𝑥4)(𝑥5𝑥6)(𝑥2𝑛1𝑥2𝑛)

would have 2𝑛 terms.

Exercise

We say a Boolean formula has a solution if its variables can be set such that the expression evaluates to 1 (i.e. true).

We then say that two boolean formulas 𝐴 and 𝐵 are equisatisfiable when 𝐴 has a solution 𝐵 has a solution. How can we convert any boolean formula of length 𝑛 into an equisatisfiable Boolean formula with length 𝒪(𝑛)?

Subset sum

As described earlier, the subset sum problem asks the following:

Given a list of 𝑛 integers and a target 𝑇, determine whether there exists a subset of the 𝑛 integers that sums to 𝑇.

The following sketch of NP-hardness is based on notes from Cornell.

To prove NP-hardness, it’s sufficient to prove that any instance of SAT can be reduced to subset sum in polynomial time. For convenience, we will assume the SAT problem is written in CNF.

Also for convenience, we assume numbers are written in a sufficiently large base 𝐵 such that no carries occur during addition. We will later place a concrete upper bound on 𝐵 and show that the total length of the list remains polynomial in the size of the SAT instance.

First, we will create two variables 𝑡𝑖, 𝑓𝑖 for each Boolean variable 𝑥𝑖. If we include 𝑡𝑖 in our subset, this will correspond to setting 𝑥𝑖 to true; similarly, 𝑓𝑖 corresponds to ¬𝑥𝑖.

Therefore, we will have to enforce the condition that for all 𝑖, exactly one of 𝑡𝑖 and 𝑓𝑖 is included in our subset. This can be done by setting the 𝑖th digit of each of 𝑡𝑖, 𝑓𝑖, and 𝑇 to be 1.

For instance, if we have three variables 𝑥1,𝑥2,𝑥3, we transform them into

𝑡1=100𝐵𝑓1=100𝐵𝑡2=010𝐵𝑓2=010𝐵𝑡3=001𝐵𝑓3=001𝐵𝑇=111𝐵

Note

Here we write exponents of 𝐵 in increasing order from left to right, so 10100𝐵 denotes 𝐵0+𝐵2.

Then, we add an additional bit for each clause in CNF. In particular, if variable 𝑥𝑖 is in clause 𝑗, we set the 𝑛+𝑗th digit of 𝑡𝑖 to 1; resp. ¬𝑥𝑖 and 𝑓𝑖. For instance, if our expression is

(𝑥1𝑥2)(𝑥1¬𝑥2¬𝑥3)

our 𝑡𝑖,𝑓𝑖 become

𝑡1=10011𝐵𝑓1=10000𝐵𝑡2=01010𝐵𝑓2=01001𝐵𝑡3=00100𝐵𝑓3=00101𝐵

Then, our condition for finding a solution is that the 4th and 5th digits of our subset sum are both at least 1. To convert this condition back into hitting an exact target sum, let 𝑘𝑗 denote the number of variables in clause 𝑗. Then, we can just add 𝑘𝑗1 copies of 𝐵𝑛1+𝑗 into our list and set our target to

𝑇=(1111𝑘1𝑘2𝑘3𝑘𝑚)𝐵

Exercise

Verify that a solution to this subset sum problem exists iff a solution exists to the original SAT instance.

It remains to show that the length of this list of numbers is polynomial in the size of the SAT instance. In order for no overflow to occur, we can choose 𝐵=2max𝑖𝑘𝑖2𝑛. Note that each number in the list has length (𝑛+𝑚)log𝐵(𝑛+𝑚)log𝑛, and therefore the total length of the list is bounded by (2𝑛+(𝑘𝑖1))(𝑛+𝑚)log𝑛𝒪(poly(length of SAT)).

NP-complete

We call a problem NP-complete if it is both NP and NP-hard.

Exercise

Are all the NP-hard problems listed above NP-complete?

Note that there are problem which are NP-hard but not NP, such as the halting problem.

Additional resources

The following video provides a really great alternative perspective on P vs NP, framing it instead as “can you efficiently invert an efficient function?” In the companion blog post, Polylog also draws a nice connection to backpropagation.