Coding Interview Patterns Module 24 – Dynamic programming: foundations
Pick-or-skip DP with adjacency rules
Choose items in a row or a ring so that no two neighbours are taken, for the largest total: the problem, its brute force and the signal for the pattern.
What you will learn
- Choose non-adjacent items in a row for the largest total with a pick-or-skip table
- Prove that skipping or taking each item covers every allowed choice
- Solve the ring version, where the first and the last items are neighbours
- Rebuild the chosen items from the table
- Solve a variant where taking a value rules out its neighbouring values
Before you start
What you’ll learn · Before you start
What you will learn
- Choose non-adjacent items in a row for the largest total with a pick-or-skip table
- Prove that skipping or taking each item covers every allowed choice
- Solve the ring version, where the first and the last items are neighbours
- Rebuild the chosen items from the table
- Solve a variant where taking a value rules out its neighbouring values
Before you start
On this page
Pick-or-skip DP chooses items from a line when taking an item rules out its neighbours: stalls along a lane, houses on a street, shifts on consecutive days. Each item is either taken or skipped, and a choice reaches only one place forward, so a table with one entry per item and two choices per entry finds the largest total in linear time. A ring adds one rule, that the first and the last items are neighbours too. This lesson poses both shapes as original problems, the row and the ring, and shows why trying every set of items cannot work.
Two problems: stalls in a row, lamps in a ring
Problem A, stalls in a row. A market lane has n stalls in a row, and stall i gives a discount of discounts[i].
Two neighbouring stalls share one counter that serves a shopper only once, so you never visit two stalls side by side.
Return the largest total discount. Limits: 0 ≤ n ≤ 100,000 and 0 ≤ discounts[i] ≤ 10,000.
[6, 2, 3, 9, 4]gives 15, from the stalls worth 6 and 9.[5, 9, 6]gives 11: the 9 is the largest, but taking it rules out both 5 and 6.[8, 1, 1, 8]gives 16, from the two ends.
Problem B, lamps in a ring. Lamp posts stand around a lake, and lamp i lights values[i] metres of the path.
Neighbouring lamps share a fuse that cannot carry both, and the last lamp is the neighbour of the first. Same limits.
[4, 1, 2, 7, 5, 3, 1]gives 14, from the lamps worth 4, 7 and 3.[9, 2, 9]gives 9: in a row the two 9s would give 18, but in the ring they are neighbours.
A row and a ring, with the best choices marked
Text description of the diagram
The upper part shows five stalls in a row with discounts 6, 2, 3, 9 and 4, each joined by a line to the stall beside it. The stalls worth 6 and 9 have a double border: they are not neighbours, and together they give the best total, 15. In a row the two end stalls are not neighbours, so both could be chosen.
The lower part shows seven lamps in a circle with values 4, 1, 2, 7, 5, 3 and 1, going clockwise from the top. Each lamp is joined to the next, and a dashed link, labelled "last and first: neighbours", joins the last lamp to the first. The lamps worth 4, 7 and 3 have a double border: no two of them are joined, and together they give the best total, 14.
The dashed link is the only difference between the two shapes: in the ring, the first and the last lamp can never both be lit.
The brute force, and why it fails
Try every set of stalls, drop the sets with two neighbours, and keep the best total:
import math
from itertools import product
def best_visit_brute(discounts):
"""Tries every set of stalls, keeps those with no two neighbours, and returns the best total and how many sets it tried."""
best, best_stalls, tried = 0, [], 0
for visit in product((False, True), repeat=len(discounts)):
tried += 1
if any(visit[i] and visit[i + 1] for i in range(len(visit) - 1)):
continue # two neighbouring stalls
total = sum(d for d, v in zip(discounts, visit) if v)
if total > best:
best, best_stalls = total, [i for i, v in enumerate(visit) if v]
return best, best_stalls, tried
for discounts in ([6, 2, 3, 9, 4], [5, 9, 6], [8, 1, 1, 8], [7]):
best, stalls, tried = best_visit_brute(discounts)
print(f"{discounts}: {best}, from stalls {stalls} ({tried} sets tried)")
def allowed_sets(n):
"""How many sets of stalls have no two neighbours, counted without listing them."""
with_last, without_last = 0, 1 # for a row of 0 stalls: only the empty set
for _ in range(n):
with_last, without_last = without_last, with_last + without_last
return with_last + without_last
for n in (10, 20, 40, 100_000):
count = allowed_sets(n)
if n < 1000:
print(f"{n:,} stalls: {count:,} allowed sets")
else:
print(f"{n:,} stalls: a number of allowed sets with {int(math.log10(count)) + 1:,} digits") Output
[6, 2, 3, 9, 4]: 15, from stalls [0, 3] (32 sets tried) [5, 9, 6]: 11, from stalls [0, 2] (8 sets tried) [8, 1, 1, 8]: 16, from stalls [0, 3] (16 sets tried) [7]: 7, from stalls [0] (2 sets tried) 10 stalls: 144 allowed sets 20 stalls: 17,711 allowed sets 40 stalls: 267,914,296 allowed sets 100,000 stalls: a number of allowed sets with 20,899 digits
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 stalls_brute.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
The answers are right, but 5 stalls already need 32 sets, and the allowed sets alone grow by a factor of about 1.6 per stall: 144 for 10 stalls, 267,914,296 for 40, and for 100,000 stalls a count with 20,899 digits. The counts are Fibonacci numbers, so no listing of them can finish at the stated size.
What to notice
- Every item has two choices, take or skip, and taking it rules out only the items just before and just after it.
- The largest item is not always taken, because it can block two neighbours worth more, as in
[5, 9, 6]. - The ring differs from the row in one pair, the first and the last item, so fixing what happens at one end turns it back into a row.
The choice at each stall, and why it is enough Pro: opens with a Pro, Premium or Ultimate pass
In this part:
- A dry run, and two numbers instead of a table
- The code in five languages
- Around the lake: the ring as two rows
- Which stalls? Rebuilding the choice from the table
- Values that rule out their neighbours: points per value
- Five bugs and the smallest inputs that break them
- Exercise
- Light the lamps around a lake, never two neighbours
- Check yourself
- A practice set with sample answers
- Interview questions
- Items stand in a row and no two neighbours may both be taken. Write the pick-or-skip recurrence for the largest total, prove it, and show with the smallest input you can find why "take the largest item left and drop its neighbours" fails.
- Items now stand in a ring, so the first and the last items are neighbours too. How do you reuse a tested solution for a row? Prove your method right, name the ring sizes that need care, and explain how to return the chosen items instead of the total.
- The stalls now form a tree: a stall's neighbours are its parent and its children, and no two neighbours may both be taken. There are 100,000 stalls, given as a parent array. What does each stall store, in what order do you fill it, and how do you avoid recursion 100,000 levels deep?
- Key takeaways
7 runnable examples (C++, Go, Java, JavaScript, Python) · 1 exercise with sample tests · 3 interview answers · about 23 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
References
- NIST Dictionary of Algorithms and Data Structures: dynamic programming (National Institute of Standards and Technology (NIST))
- NIST Dictionary of Algorithms and Data Structures: Fibonacci number (National Institute of Standards and Technology (NIST))
- collections.Counter (The Python Standard Library) (Python Software Foundation)
- itertools.product (The Python Standard Library) (Python Software Foundation)
- The Java Language Specification, Java SE 25: 4.2.1 Integral types and values (Oracle)
- The Go Programming Language Specification: Slice expressions (The Go Authors)
- ECMAScript 2026 Language Specification: Array.prototype.slice (Ecma International)
- AddressSanitizer (Clang documentation) (The LLVM Project)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress