Run it
Three implementations checked against each other, including the empty list.
Why greedy fails, on the case that shows it.
The tuple assignment that is load-bearing, and the circular variant.
The circular variant handles the wraparound by running the linear solution twice, once excluding each end house.
Partition by the last decision
The same move as every other question in this group: look at the final choice. Either the last house was robbed or it was not.
If it was not, the answer is whatever was best for the houses before it — best(i-1). If it was, its neighbour cannot have been, so the answer is its value plus the best for everything up to two houses back — best(i-2) + house[i]. Those two cases are exhaustive and disjoint, so the answer is the larger.
The base cases are worth stating explicitly, because off-by-ones here are common: best(0) = 0 (no houses) and best(1) = house[0]. Indexing the table from 1 while the list is indexed from 0 is where the confusion comes from, and the rolling version below makes it disappear.
Why greedy fails, and what to check
"Take every other house" is the intuition, and it is wrong: [2, 1, 1, 2] gives 3 taking the odd positions, where taking the two 2s gives 4. "Always take the biggest remaining" is also wrong, for the same reason it fails in coin change — no lookahead.
The editor checks the DP against brute force over every valid subset on several inputs, including the empty list and a single house. That is worth doing rather than asserting, because the recurrence is short enough to look obviously right while being wrong at the edges.
The rolling version, and why it is the one to write
skip = take = 0
for h in houses:
skip, take = max(skip, take), skip + h
return max(skip, take)
Two names, one pass, no indices. skip is the best with the current house not taken, take the best with it taken — and the simultaneous assignment is what makes it correct: take uses the old skip, which is exactly the two-houses-back value.
Writing it in two steps instead, without the tuple assignment, is the bug: updating skip first means take adds the current house to a value that already includes its neighbour. It is the same hazard as swapping without a temporary.
The circular variant
"Now the houses are in a circle." The first and last are adjacent, so they cannot both be robbed. The neat resolution is to run the linear solution twice: once on houses[1:] and once on houses[:-1], then take the better. Every valid selection excludes at least one of the two ends, so one of the runs contains the optimum.
The single-house case needs a guard, because both slices are empty and the answer should be that house. That edge is the whole difficulty of the variant, and volunteering it is what an interviewer is listening for.
Edge cases to raise
Volunteering these is most of what separates a correct answer from a good one.
Empty list. Zero, and a table version indexing best[1] raises before the loop starts.
One house. Take it. The circular variant needs this as a special case, because both of its slices are empty.
Negative values. The recurrence still works, because skipping is always permitted - worth checking rather than assuming, and worth asking about if the problem says 'money'.