Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Data Structures & Algorithms (DSA)  Module 14 – Dynamic programming

1-D dynamic programming: take or skip

Solve sequence problems with one table entry per position: take-or-skip choices traced on a street, two variables instead of a table, and the choice rebuilt.

  • Intermediate
  • 30 minutes
  • Examples run with Python 3.14.8, Pyodide 314.0.7, Node.js 24.21.0, quickjs 0.32.0, Java 25, Clang 21.0.0 and Go 1.27.1
  • By MySmartCoPilot
Free core, Pro depth: 10 of 30 minutes free
Free: A street of donations · Skip or take: the recurrence · The table, traced on the street · 3 more sections. With a pass: 3 in-depth parts and 3 interview answers, about 20 minutes.

What you will learn

  • Solve linear DP problems with take-or-skip transitions
  • Optimise the memory of a linear DP from O(n) to O(1)
  • Rebuild the chosen elements from the table by walking back
  • Recognise the same one-dimensional shape in counting and scheduling problems

Before you start

One-dimensional dynamic programming (1-D DP) solves a problem over a sequence with one table entry per position: best[i] is the answer for the first i items, and each entry is worked out from a few entries just before it, so one pass over the sequence fills the table in O(n) time. The most common shape is take or skip. On a street where two neighbouring houses may not both be visited, house i is either skipped, which leaves best[i − 1], or taken, which adds its gift to best[i − 2], the best that stops short of its neighbour. So best[i] is the larger of best[i − 1] and best[i − 2] + gift(i). Because each entry looks back only two places, two variables can replace the table, and walking back through the table tells you which houses were chosen. This lesson traces the recurrence on a street of eight houses, runs it, cuts its memory to two numbers and rebuilds the choice.

A street of donations

A volunteer collects donations along a street of eight houses. The housing society asks that no two neighbouring houses are visited on the same day, and the gifts on offer are 40, 10, 60, 50, 20, 90, 30 and 70. Which houses should the volunteer visit to collect the most?

The obvious plans fall short. Every other house gives 40 + 60 + 20 + 30 = 150 from the odd houses, or 220 from the even ones. Trying every allowed set works for eight houses, which allow only 55 sets, but the count grows like the Fibonacci numbers, by about 1.6 for each extra house: a street of 100 houses allows about 9.3 × 10²⁰ of them.

Skip or take: the recurrence

Write the DP in its four parts. The state is best[i], the most that the first i houses can give. The transition looks at house i, the last of them, which is either skipped or taken:

best[i] is the larger of skipping house i, best[i − 1], and taking it, gift(i) + best[i − 2]; for house 8 that is 260.best[i]the most from the first i housesSkip house ibest[i − 1]Take house igift(i) + best[i − 2]house i − 1 stays unvisitedbest[i] = the larger of the twoHouse 8 of the street:skip 190, take 70 + 190 = 260best[8] = 260for example

Every house is skipped or taken

Text description of the diagram

The diagram shows the one decision that 1-D DP makes for each house, from top to bottom.

  1. best[i] is the most that the first i houses can give.
  2. Skipping house i keeps best[i − 1], the most from the houses before it.
  3. Taking house i adds its gift to best[i − 2], because house i − 1, its neighbour, must then stay unvisited.
  4. best[i] is the larger of the two.

For house 8 of the lesson's street, skipping it keeps 190 and taking it gives 70 + 190 = 260, so best[8] = 260.

The base cases are best[0] = 0 for an empty street and best[1] = gift(1), which the rule also gives if best[−1] is read as 0. The answer is best[n]. Filling i from 1 up to n keeps every entry after the two it reads.

The table, traced on the street

The table for the street, the houses it chose and three checks Python · street/street.py
"""The most donations along a street when two neighbouring houses may not both be visited."""
import random
from itertools import product

donations = [40, 10, 60, 50, 20, 90, 30, 70]


def best_table(d):
    """best[i]: the most that the first i houses can give; best[0] = 0, a street with no houses."""
    best = [0] * (len(d) + 1)
    for i in range(1, len(d) + 1):
        skip = best[i - 1]
        take = (best[i - 2] if i >= 2 else 0) + d[i - 1]
        best[i] = max(skip, take)
    return best


def chosen_houses(d, best):
    """Walk back from the last house: an entry equal to the one before it means the house was skipped."""
    houses, i = [], len(d)
    while i >= 1:
        if best[i] == best[i - 1]:
            i -= 1
        else:
            houses.append(i)
            i -= 2
    return houses[::-1]


def best_two_numbers(d):
    before, last = 0, 0  # best[i - 2] and best[i - 1]
    for x in d:
        before, last = last, max(last, before + x)
    return last


def every_selection(d):
    best = 0
    for picks in product((0, 1), repeat=len(d)):
        if not any(picks[i] and picks[i + 1] for i in range(len(d) - 1)):
            best = max(best, sum(x for x, p in zip(d, picks) if p))
    return best


best = best_table(donations)
print(f"{'house':>5} {'gift':>5} {'skip':>5} {'take':>5} {'best':>5}")
for i in range(1, len(donations) + 1):
    take = (best[i - 2] if i >= 2 else 0) + donations[i - 1]
    print(f"{i:>5} {donations[i - 1]:>5} {best[i - 1]:>5} {take:>5} {best[i]:>5}")
houses = chosen_houses(donations, best)
print(f"houses visited: {houses}, total {sum(donations[h - 1] for h in houses)}")
print(f"two numbers instead of a table: {best_two_numbers(donations)}")
print(f"every allowed selection tried: {every_selection(donations)}")

rng = random.Random(5)
streets = [[rng.randint(0, 99) for _ in range(rng.randint(0, 12))] for _ in range(2_000)]
agree = all(best_table(s)[-1] == best_two_numbers(s) == every_selection(s) for s in streets)
print(f"2,000 random streets of 0 to 12 houses, table = two numbers = every selection: {agree}")

Output

house  gift  skip  take  best
    1    40     0    40    40
    2    10    40    10    40
    3    60    40   100   100
    4    50   100    90   100
    5    20   100   120   120
    6    90   120   190   190
    7    30   190   150   190
    8    70   190   260   260
houses visited: [1, 3, 6, 8], total 260
two numbers instead of a table: 260
every allowed selection tried: 260
2,000 random streets of 0 to 12 houses, table = two numbers = every selection: True

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 street.py

Read a few rows. At house 3, taking it gives 60 + best[1] = 100, more than the 40 of skipping it. At house 4, taking gives only 50 + 40 = 90, so best[4] stays 100. House 6 is worth taking, 90 + 100 = 190, and house 8 lifts the total to 70 + 190 = 260.

The table stores totals, not houses, but the houses can be read back from it. Start at the last house. If best[i] equals best[i − 1], house i can be skipped without loss, so move to i − 1. Otherwise house i was taken: record it and jump to i − 2, past its neighbour. Here the walk takes house 8, jumps to 6 and takes it, skips 4, takes 3, jumps to 1 and takes it: houses 1, 3, 6 and 8 for 260. The run checks that answer against all 55 allowed sets, and the table against the same search on 2,000 random streets.

Two numbers instead of a table

Each entry reads only the two entries before it, so once best[i] is known, best[i − 2] is never read again. Keeping two variables, before and last, cuts the memory from O(n) to O(1) and still gives 260. The price is the walk back, which needs every entry: keep the table, or one bit per house that records the choice, when the houses matter, and the two numbers when only the total does.

The same shape in other problems

Many problems are this table with a different look-back:

  • Digit decodings. Counting the ways to read a string of digits as letters adds the two entries before each position, but only when those digits form a letter.
  • A circular street. When the first and last houses are neighbours too, the answer is the better of two straight streets.
  • Weighted interval scheduling. For paid gigs with start and end times, taking a gig looks back to the last gig that ends before it starts.
  • Rest days. If every working day needs k rest days after it, taking a day looks back k + 1 places, which the exercise asks you to write.

Exercise

Exercise · Medium · Python

Plan working days with k rest days after each

A freelance sound engineer is offered one paid job on each day of a festival season: values[d] is what day d would pay, and every value is at least 0. After any working day they must rest for at least k days before working again, so with k = 2 they could work days 0, 3 and 6, but not days 0 and 2.

Write best_days(values, k), which returns the largest total they can earn. The starter code solves only k = 1, the street of houses from the lesson. Change its transition so that taking day i looks back far enough for any k ≥ 0, and keep it O(n) in time.

The sample tests check k = 1, k = 0 (no rest needed at all), k = 2, a k longer than the season, an empty season, and 300 random seasons against a slow function that tries every set of days.

Starter code · rest_days.py

def best_days(values, k):
    """The largest total of values[d] over working days d, with at least k rest days after each working day."""
    # This solves k = 1 only: taking day i adds to the best up to two days earlier.
    # Change how far back "take" looks so that it works for any k >= 0.
    before, last = 0, 0
    for v in values:
        before, last = last, max(last, before + v)
    return last
The sample tests · test_rest_days.py
import random
from itertools import combinations

from rest_days import best_days


def slow_best(values, k):
    """Every set of days, kept when consecutive working days are at least k + 1 apart."""
    best = 0
    for r in range(len(values) + 1):
        for days in combinations(range(len(values)), r):
            if all(b - a > k for a, b in zip(days, days[1:])):
                best = max(best, sum(values[d] for d in days))
    return best


def test_one_rest_day():
    """k = 1 is the street of houses: no two neighbouring days"""
    assert best_days([40, 10, 60, 50, 20, 90, 30, 70], 1) == 260


def test_no_rest_needed():
    """k = 0: every day can be a working day"""
    assert best_days([3, 1, 4, 1, 5], 0) == 14


def test_two_rest_days():
    """k = 2: days 0, 3 and 6; and two of the three 9s, since 9s two days apart are too close"""
    assert best_days([5, 1, 1, 5, 1, 1, 5], 2) == 15
    assert best_days([9, 1, 9, 1, 9], 2) == 18


def test_rest_longer_than_the_season():
    """a k longer than the season leaves room for one working day: the best single day"""
    assert best_days([4, 8, 2, 6], 10) == 8


def test_empty_season():
    """no days, no earnings"""
    assert best_days([], 3) == 0


def test_against_every_set_of_days():
    """300 random seasons of up to 10 days and k from 0 to 4"""
    rng = random.Random(12)
    for _ in range(300):
        values = [rng.randint(0, 20) for _ in range(rng.randint(0, 10))]
        k = rng.randint(0, 4)
        assert best_days(values, k) == slow_best(values, k), (values, k)
A hint

Use a table best where best[i] is the most the first i days can earn, with best[0] = 0. Skipping day i gives best[i - 1]; working it gives values[i - 1] plus the best of the days that end at least k days before it, which is best[i - k - 1], or 0 when that index is below 0.

The sample tests run on this device, in your browser (Pyodide): nothing is sent to mysmartcopilot.com. The first run downloads Python (about 13.5 MB), which is kept for the next runs. A check in your browser is feedback for you, not proof that the code is right for every input.

Python Online Compiler Change the gifts in street.py, or try every-other-house plans by hand, and compare them with best[n].

Check yourself

3 questions about this lesson. Every answer and why it is right is on the page, behind “Show the answer”. Your score stays in this browser.

  1. Question 1 of 3 On the street with gifts 40, 10, 60, 50, 20, 90, 30 and 70, best[4] = 100 and best[3] = 100. What is best[5], the most from the first five houses?

    Type a number.

    Show the answer to question 1

    Answer: 120

    Skipping house 5 keeps best[4] = 100. Taking it adds its gift, 20, to best[3] = 100, which gives 120. The larger of the two is 120: houses 1, 3 and 5.

  2. Question 2 of 3 Why can two variables replace the whole table when only the best total is needed?

    Choose one answer.

    Show the answer to question 2

    Answer: Each entry reads only the two entries just before it

    best[i] = max(best[i − 1], best[i − 2] + gift(i)) looks back exactly two places, so once best[i] is known, best[i − 2] is never read again. Keeping before and last is enough for the total; the walk back to the houses still needs the table.

  3. Question 3 of 3 Walking back from the end of the table, best[i] equals best[i − 1]. What does that say about house i?

    Choose one answer.

    Show the answer to question 3

    Answer: Skipping it is as good as anything, so the walk skips it and moves to house i − 1

    If best[i] equals best[i − 1], some best selection of the first i houses leaves house i out, so the walk skips it. Only when best[i] is larger did taking house i make the difference; then the walk records it and jumps to i − 2, past its neighbour.

Why take-or-skip is right, and four bugs that break it Pro: opens with a Pro, Premium or Ultimate pass

The recurrence has a short proof, and most wrong answers come from one of four slips. This part proves the table correct and runs each slip against it until it fails, with the smallest street that shows the failure.

In this part:

  • The proof: a best selection skips the last house or takes it
  • Four plausible bugs, each with the smallest street that breaks it
  • Edge cases worth a test

1 runnable example (Python) · about 3 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.

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).

Digit decodings, a circular street and paid gigs, solved and checked Pro: opens with a Pro, Premium or Ultimate pass

Three problems that look different are the same one-dimensional table with a different look-back. Each is solved here and checked against a brute force on hundreds or thousands of random inputs.

In this part:

  • Counting the readings of a digit string, zeros included
  • A circular street as two straight streets
  • Weighted interval scheduling in O(n log n), with the gigs rebuilt

4 runnable examples (Python) · about 3 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.

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).

The street in five languages, and the 32-bit total that breaks it Pro: opens with a Pro, Premium or Ultimate pass

The table and the walk back are a few lines in every interview language, and all five versions print the same lines. One extra run in Java shows what a 32-bit total does on a long street.

In this part:

  • The code in Python, JavaScript, Java, C++ and Go
  • A long street: totals past 2³¹ − 1, reproduced in Java
  • Practice set
  • Key takeaways of these parts

2 runnable examples (Build and run all three, C++, Go, Java, JavaScript, Python) · about 2 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.

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).

Interview questions

  1. Fresher · Warm-up

    In the take-or-skip recurrence for a street of houses, why does taking house i add best[i − 2] rather than best[i − 1], and what are the base cases?

    Taking house i rules out house i − 1, its neighbour, so the rest of the selection must come from the first i − 2 houses, and the most they can give is best[i − 2]. best[i − 1] may already include house i − 1, so adding house i to it could visit two neighbours: on the street 1, 1 that slip returns 2 instead of 1. Skipping house i leaves the first i − 1 houses free, which is best[i − 1]. The base cases are best[0] = 0, a street with no houses, and best[1] = gift(1), which the rule also gives if best[−1] counts as 0. In 0-indexed code the second entry is the larger of the first two gifts, not the second gift: the second gift alone is wrong whenever the first is larger, as on the street 2, 1.

  2. Fresher to mid

    A teammate's solution visits every other house, starting from whichever of the first two houses gives more. It passes all five sample tests. Find the smallest street that breaks it, explain why, and replace it with an O(n) solution. What memory does yours need to also return the houses?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. Fresher to mid

    Your function counts the ways to read a string of digits as letters, with 1 = A up to 26 = Z. It is right on 12 and 226 but wrong on 10, 100 and 2101. What do you fix in the transition and the base cases, and how do you test the fix?

    Model answer, Pro. First step and follow-ups

  4. Mid to senior

    A booking service picks non-overlapping paid gigs with the largest total, from a table over gigs sorted by end time, and finds each gig's last compatible gig by scanning backwards. It is instant on a week of short gigs but takes minutes on a year of long, overlapping ones. Explain why with numbers, fix it and return the gigs.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • 1-D DP keeps one entry per position, best[i] for the first i items, and fills it in one pass.
  • Take or skip: best[i] is the larger of best[i − 1] and best[i − 2] + gift(i), with best[0] = 0.
  • On the lesson’s street the table finds 260 from houses 1, 3, 6 and 8, where every other house gives at most 220.
  • Two variables are enough for the total; the walk back to the chosen items needs the table or a record of choices.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.