Skip to content
academia.sh

Lesson 09 / 10

The P vs NP Question

Stating the open question and laying out the only thing that can be measured: the known lower bound spends 240 steps, meet-in-the-middle 1357, exhaustive search 4321, and the two methods agree on 20 of 20 examples. At input size 24, the lower bound is 24, meet-in-the-middle 8192, exhaustive search 16,777,216; the gap's ratio runs from 341.3 to 699,050.7. At a budget of one million steps, exhaustive search reaches 19 numbers, meet-in-the-middle 37. The question is open, and no direction is claimed in this lesson.

Contents

Four class names and the known relationships between them have been established. What remains is the most talked-about, least answered question: is everything verifiable also solvable. This lesson states that question and does not try to measure what it cannot measure.

Why the question cannot be measured stays visible throughout the lesson. Measurement is a finite run, and a finite run can only say “this method spent this many steps.” The question, though, carries a quantifier over all methods: “does no method exist.” The distance between these two sentences is this lesson’s subject. The only thing that can be measured is the gap between the best known method and the known lower bound.

  • CC38. The question is this containment relationship: is class P equal to the whole of class NP. That P sits inside NP was established in 02; what is asked is the reverse direction.
  • CC39. The question is open. No direction is claimed in this lesson, considered likely, or called “expected.”
  • CC40. A known lower bound is a shown step count that no method can go below. The lower bound used here is the weakest one: since a decision cannot be made without reading the input, at least n steps.
  • CC41. The best known method is whichever of the methods built in this lesson spends the fewest steps. The word “known” is read relative to this course’s scope; whether a better one exists outside the course is not measured here.
  • CC42. The second method is meet-in-the-middle: the input is split in two, all of the first half’s subset sums are written into a table, and the second half is queried against that table.
  • CC43. The correctness of meet-in-the-middle is not assumed; it is compared against exhaustive search’s answer on every example.
  • CC44. A step is one subset for exhaustive search, one subset or one query for meet-in-the-middle, and one number read for the lower bound.
  • CC45. The budget sweep is done at four values: 13, 100, 1000, 10,000 steps. The second sweep is over input size.
  • CC46. Examples come from the shared definition’s generator, seed 20260218. There is no second seed.
  • CC47. A gap narrowing does not count as a sign of direction. The narrowing is measured, not interpreted.

The Question Itself

The statement is short and unambiguous. P is the class of decision problems solved in a number of steps bounded by a polynomial in input size. NP is the class of problems whose “yes” answer is verified within the same bound. Since every solved problem is verifiable, P sits inside NP. What is asked is: is there a problem inside NP that is not in P.

One direction of the question has been established, and it is easy to show. The other direction could close in two ways: either a polynomial-step method is found for an NP-complete problem and the two classes coincide, or it is proven that polynomial steps are insufficient for such a problem and they separate. Neither has been done, and this lesson says nothing about which will happen.

Why a run cannot close this question is equally clear. A method spending 4096 steps does not show that 4096 steps are required for that problem; it only shows that that method spent that many. A lower-bound proof, by contrast, speaks about every method, and no run can exhaust every method. This course’s ban on overclaiming exists exactly to guard this gap.

The Only Thing That Can Be Measured

The block below places three numbers side by side: the known lower bound, the steps of the best method built in this course, and exhaustive search’s steps. Meet-in-the-middle’s answer is compared against exhaustive search on every example.

SEED = 20260218


def examples(seed=SEED, n=12, count=20):
    d = seed
    result = []
    for _ in range(count):
        numbers = []
        for _ in range(n):
            d = (d * 1103515245 + 12345) % 2147483648
            numbers.append(d % 97 + 3)
        d = (d * 1103515245 + 12345) % 2147483648
        result.append({"numbers": numbers, "target": sum(numbers) // 3 + d % 7})
    return result


def brute_force(numbers, target):
    """One step = one subset."""
    steps, n = 0, len(numbers)
    for mask in range(1 << n):
        steps += 1
        if sum(numbers[i] for i in range(n) if mask >> i & 1) == target:
            return True, steps
    return False, steps


def meet_in_middle(numbers, target):
    """Input is split in two; every half's subset sums are worked out
    and the second half is queried against them. One step = one subset or one query."""
    n = len(numbers)
    left, right = numbers[: n // 2], numbers[n // 2:]
    steps, table = 0, set()
    for mask in range(1 << len(left)):
        steps += 1
        table.add(sum(left[i] for i in range(len(left)) if mask >> i & 1))
    for mask in range(1 << len(right)):
        steps += 1
        t = sum(right[i] for i in range(len(right)) if mask >> i & 1)
        if target - t in table:
            return True, steps
    return False, steps


EX = examples()
agree, bfs, mms, lbs = 0, 0, 0, 0
for o in EX:
    y1, a1 = brute_force(o["numbers"], o["target"])
    y2, a2 = meet_in_middle(o["numbers"], o["target"])
    agree += y1 == y2
    bfs, mms, lbs = bfs + a1, mms + a2, lbs + len(o["numbers"])
print("20 examples, n=12 | two methods agree:", agree, "/ 20")
print("  known lower bound:", lbs, "steps | meet-in-the-middle:", mms,
      "steps | exhaustive search:", bfs, "steps")
print()
print("budget  exhaustive search  meet-in-the-middle")
for b in (13, 100, 1000, 10000):
    c1 = sum(1 for o in EX if brute_force(o["numbers"], o["target"])[1] <= b)
    c2 = sum(1 for o in EX if meet_in_middle(o["numbers"], o["target"])[1] <= b)
    print(f"{b:5d}  {c1:10d}  {c2:15d}")
print()
print(" n  lower bound  meet-in-middle  exhaustive search  meet/lower bound  search/lower bound")
for n in (8, 12, 16, 20, 24):
    mim = (1 << (n - n // 2)) + (1 << (n // 2))
    print(f"{n:2d}  {n:9d}  {mim:7d}  {1 << n:10d}  {mim / n:17.1f}"
          f"  {(1 << n) / n:16.1f}")
20 examples, n=12 | two methods agree: 20 / 20
  known lower bound: 240 steps | meet-in-the-middle: 1357 steps | exhaustive search: 4321 steps

budget  exhaustive search  meet-in-the-middle
   13           0                0
  100           7               20
 1000          19               20
10000          20               20

 n  lower bound  meet-in-middle  exhaustive search  meet/lower bound  search/lower bound
 8          8       32         256                4.0              32.0
12         12      128        4096               10.7             341.3
16         16      512       65536               32.0            4096.0
20         20     2048     1048576              102.4           52428.8
24         24     8192    16777216              341.3          699050.7

Reading the Gap

Three numbers side by side: lower bound 240, meet-in-the-middle 1357, exhaustive search 4321. The second method spends less than a third of the first and gives the same answer on 20 of 20 examples. This is an improvement, it was measured, and it is real.

The budget sweep shows where the improvement pays off. At budget 100, exhaustive search stays at 7 examples while meet-in-the-middle gives 20 of 20. At budget 1000, exhaustive search rises to 19; at 10,000 both are full. So there is a budget range where the improvement is visible, and outside that range the two methods cannot be told apart.

The input-size table answers the real question: does the gap close. The meet-in-the-middle column is 32, 128, 512, 2048, 8192 — quadrupling at every row. The exhaustive search column goes from 256 to 16,777,216, multiplying by sixteen at every row. The ratios to the lower bound climb from 4.0 to 341.3, and from 32.0 to 699,050.7.

What must be read is that both columns grow. The improvement did not reduce the ratio; it reduced the rate of growth. At twenty-four numbers, meet-in-the-middle is 2048 times cheaper than exhaustive search, but still 341 times more expensive than the lower bound. The gap narrowed, it did not close, and this table carries no sign that it will close — nor any sign that it will not.

Does the Improvement Hold in Both Directions

The 3.18-fold gain on the yes set could partly come from stopping early: exhaustive search stops once it finds a matching subset, and this gives it a real discount, not an unfair advantage. Whether the gain comes from structure or from stopping early is measured on a set where stopping early never happens. Lesson 04’s unstructured no examples are exactly that.

SEED = 20260218


def examples(seed=SEED, n=12, count=20):
    d = seed
    result = []
    for _ in range(count):
        numbers = []
        for _ in range(n):
            d = (d * 1103515245 + 12345) % 2147483648
            numbers.append(d % 97 + 3)
        d = (d * 1103515245 + 12345) % 2147483648
        result.append({"numbers": numbers, "target": sum(numbers) // 3 + d % 7})
    return result


def brute_force(numbers, target):
    steps, n = 0, len(numbers)
    for mask in range(1 << n):
        steps += 1
        if sum(numbers[i] for i in range(n) if mask >> i & 1) == target:
            return True, steps
    return False, steps


def meet_in_middle(numbers, target):
    n = len(numbers)
    left, right = numbers[: n // 2], numbers[n // 2:]
    steps, table = 0, set()
    for mask in range(1 << len(left)):
        steps += 1
        table.add(sum(left[i] for i in range(len(left)) if mask >> i & 1))
    for mask in range(1 << len(right)):
        steps += 1
        t = sum(right[i] for i in range(len(right)) if mask >> i & 1)
        if target - t in table:
            return True, steps
    return False, steps


def unreachable(numbers):
    """Setup step: an unreachable target is chosen; the answer is definitely 'no'."""
    reached = {0}
    for x in numbers:
        reached |= {u + x for u in reached}
    return min((t for t in range(1, sum(numbers)) if t not in reached),
               key=lambda t: abs(t - sum(numbers) // 3))


EX = examples()
YES = [(o["numbers"], o["target"]) for o in EX]
NO = [(o["numbers"], unreachable(o["numbers"])) for o in EX]
print("set    agree  exhaustive search  meet-in-middle  ratio")
for name, batch in (("yes  ", YES), ("no   ", NO)):
    agree = bfs = mms = 0
    for s, h in batch:
        y1, a1 = brute_force(s, h)
        y2, a2 = meet_in_middle(s, h)
        agree += y1 == y2
        bfs, mms = bfs + a1, mms + a2
    print(f"{name}  {agree:6d}  {bfs:10d}  {mms:7d}  {round(bfs / mms, 2):5}")
set    agree  exhaustive search  meet-in-middle  ratio
yes        20        4321     1357   3.18
no         20       81920     2560   32.0

On the no set, neither method can stop early, so both do the whole of their work. Exhaustive search spends 81,920, meet-in-the-middle 2560 steps, a ratio of 32.0. This is ten times the 3.18 on the yes set. The conclusion is: the source of the gain is not stopping early, it is the method’s structure; the ratio looking low on the yes set is because exhaustive search gets a discount there.

This distinction bears on the open question. Whether an improvement is genuinely structural can only be seen by measuring it on a discount-free set. Even so, the 32.0 ratio, too, points to no direction: an exponential divided by a constant factor is still an exponential, and the table already showed this with a 341-fold distance at twenty-four numbers.

How Far You Get with a Fixed Budget

The engineering counterpart of the improvement is not the ratio but the input size reached. A step budget can be fixed, and the largest input size each method can handle within that budget computed. The table below carries a third column, and that column is hypothetical: what would happen if a quadratic-step method were found. No claim is made that such a method exists; the column only shows what the question would change.

def largest_n(budget, cost, upper=200000):
    """The largest input size that stays within budget."""
    best = 0
    for n in range(1, upper + 1):
        if cost(n) <= budget:
            best = n
    return best


def scan(n):
    return 1 << n


def meet(n):
    return (1 << (n - n // 2)) + (1 << (n // 2))


def quadratic(n):
    return n * n


print("step budget      exhaustive search  meet-in-middle  hypothetical n^2")
for b in (10 ** 4, 10 ** 6, 10 ** 8, 10 ** 10):
    print(f"{b:14d}  {largest_n(b, scan):10d}  {largest_n(b, meet):7d}"
          f"  {largest_n(b, quadratic):15d}")
step budget      exhaustive search  meet-in-middle  hypothetical n^2
         10000          13       24              100
       1000000          19       37             1000
     100000000          26       50            10000
   10000000000          33       64           100000

As the budget rises ten-thousandfold, exhaustive search’s reach goes from 13 to 33: the budget grows tens of thousands of times, input size gains twenty numbers. Meet-in-the-middle, over the same budgets, goes from 24 to 64, meaning at every budget it stretches to roughly twice the input size of exhaustive search. This is a real, measured gain.

The third column shows the nature of the difference. The hypothetical quadratic method, over the same budgets, goes from 100 to 100,000. The first two columns gain a handful of numbers by growing the budget; the third gains orders of magnitude. The engineering counterpart of the P vs NP question is the gap between these two behaviors: the first and second columns belong to the same family, the third belongs to a different one, and which family the subset sum problem belongs to is not known.

What This Lesson Does Not Say

None of the numbers above points in a direction, and none should be read as if it did. Meet-in-the-middle beating exhaustive search 2048-fold does not suggest something better will be found; meet-in-the-middle staying 341 times away from the lower bound does not suggest it will not. Measurement is not the kind of thing that could support either sentence.

What can be said is exactly this. The best method built in this lesson spent 1357 steps at 12 numbers; the known lower bound was 240 steps; the ratio between them rose to 341.3 at input size 24. This sentence is measured and written with its budget attached. The sentence “subset sum cannot be solved in polynomial time” is not measured, has not been proven in this course, and has not been proven by anyone; writing it would violate the rule.

The same strictness applies to the reverse direction. The sentence “one day a polynomial method will be found” is not measured either. The only honest sentence that can be written about an open question is the sentence stating that the question is open.

The Engineering Counterpart

The question staying open does not leave an engineer without work; on the contrary, it makes clear what to do. If a problem is known to reduce to an NP-complete problem, searching for a general, cheap solution to it is the same thing as trying to solve an unsolved question in the theory. This does not mean it cannot be done; it means correctly naming what is being attempted.

In practice three paths remain, and all three have been or will be measured in this course. The first is keeping the input small: the budget table showed which method suffices at 12 numbers. The second is improving the method: meet-in-the-middle doubled the input size reached. The third is giving up on an exact solution, and this is the subject of the next lesson.

Summary

  • The P vs NP question asks whether a problem exists inside NP that is not in P, and it is open; this lesson claims no direction.
  • A finite run cannot close the question, because it measures one method’s steps and cannot speak about every method.
  • On twenty examples the known lower bound is 240, meet-in-the-middle 1357, exhaustive search 4321 steps; the two methods agree on 20 of 20 examples.
  • As input size rises from 8 to 24, meet-in-the-middle goes from 32 to 8192, exhaustive search from 256 to 16,777,216; the ratio to the lower bound climbs from 4.0 to 341.3 and from 32.0 to 699,050.7. The gap narrowed, it did not close.
  • At a budget of one million steps, exhaustive search reaches 19 numbers, meet-in-the-middle 37; a hypothetical quadratic method would have reached 1000.
  • The only honest sentence that can be written about an open question is one that states the measured gap with its budget and says the question is open.

Next Step

This lesson left the third path open: giving up on an exact solution. The next lesson counts its cost. On how many examples does an approximate solution find the exact result, how far off does it fall when it doesn’t, and when the budget given to the approximate method is grown, does the loss really close. The course’s and the Computer Science curriculum’s final lesson ends with this question.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ Esc navigate · open · close