Chapter 14
The Puzzle Of Misanthropic Neighbours
The misanthropes are coming. Suppose there is a row of some number, , of houses in a new, initially empty development. Misanthropes are moving into the development one at a time and selecting a house at random from those that have nobody in them and nobody living next door. They keep on coming until no acceptable houses remain. At most, one out of two houses will be occupied; at least one out of three houses will be. But what’s the expected fraction of occupied houses as the development gets larger, that is, as goes to infinity?
The Riddler, FiveThirtyEight(original post)
Solution
Let be the expected number of occupied houses in a clean row of , meaning a block with empty houses and no occupied neighbour just outside either end. The first misanthrope picks one of the houses uniformly. If they take house , that house is occupied and houses and become forbidden, splitting what remains into two independent clean rows of lengths and . Averaging over , and using that the two pieces contribute symmetrically,
To extract the large- behaviour, gather the into the generating function . Multiplying the recurrence by and summing turns it into the differential equation a first-order linear ODE. Its integrating factor is , and solving with gives the closed form Now read off the density. The factor carries coefficients growing like , while the analytic factor tends to as . So converges to that constant, and the limiting occupied fraction is This sits, as promised, between and .
The computation
Run the development directly: repeatedly pick a uniformly random house that is empty with empty neighbours, occupy it, forbid its neighbours, and continue until none remain. For a long row the occupied fraction settles near .
import numpy as np
rng = np.random.default_rng(0)
n, runs = 2000, 4000
total = 0
for _ in range(runs):
free = set(range(n)); occupied = 0
while free:
h = rng.choice(list(free)) # a random allowed house
occupied += 1
free.discard(h)
free.discard(h - 1); free.discard(h + 1) # forbid neighbours
total += occupied
print(total / (n * runs)) # ~0.4323 = (1 - e**-2) / 2