Pattern 13 of 20

1-D Dynamic Programming

The answer for i depends on a few earlier answers: define the state, write the recurrence.

When to use it

  • Count the ways, or find the minimum or maximum, over a sequence
  • A recursive solution recomputes the same subproblems
  • Choices at each position: take it or skip it

The idea

Name the subproblem precisely (the best answer ending at, or using the first, i items), express it through smaller ones, and fill a table from the base cases up. Often only the last two values are needed.

Typical cost O(n) or O(n·k) time, often O(1) space after optimisation.

How it works

Dynamic programming is recursion that refuses to solve the same subproblem twice. If a brute-force recursion branches into calls whose arguments repeat, store each answer the first time and look it up afterwards. That is memoization; filling a table in order is the same idea bottom-up.

In one-dimensional DP the state is a single index: the best answer for the first i items, or for a target value i. Everything hinges on writing the recurrence: how dp[i] is built from a few earlier entries. Say it in words first: 'the best up to house i is either skipping house i or robbing it plus the best up to i - 2'.

The recurrence works when the problem has optimal substructure, meaning the best answer for i really is assembled from best answers for smaller inputs, and when the number of distinct states is small enough to tabulate.

Look for 'number of ways', 'minimum cost', 'maximum value', or 'is it possible' over a sequence where each step makes a small choice. Climbing stairs, house robbing, coin change, word break and longest increasing subsequence all fit.

How to attack it

  1. 1Define dp[i] in one precise sentence.
  2. 2Write the recurrence from the last choice made at i.
  3. 3Set the base cases and the iteration order so dependencies are ready.
  4. 4Decide where the answer lives (dp[n], or the max over all i).
  5. 5Shrink the table to a few variables if dp[i] only uses the last few entries.

Template

A skeleton to adapt, not a solution to paste. Write it from memory a few times.

from functools import lru_cache


def max_non_adjacent(nums):
    # dp[i] = best total using items 0..i-1.
    # dp[i] = max(dp[i-1], dp[i-2] + nums[i-1]); only two values are needed.
    prev2, prev1 = 0, 0
    for x in nums:
        prev2, prev1 = prev1, max(prev1, prev2 + x)
    return prev1


def min_coins(coins, amount):
    INF = amount + 1
    dp = [0] + [INF] * amount          # dp[a] = fewest coins to make a
    for a in range(1, amount + 1):
        for c in coins:
            if c <= a:
                dp[a] = min(dp[a], dp[a - c] + 1)
    return dp[amount] if dp[amount] != INF else -1


def ways_top_down(n):
    @lru_cache(maxsize=None)            # memoization: each n solved once
    def go(i):
        if i <= 1:
            return 1
        return go(i - 1) + go(i - 2)
    return go(n)

Variants

Fibonacci-style
dp[i] depends on dp[i-1] and dp[i-2]: stairs, tilings, decoding digit strings. Two variables suffice.
Take or skip
Each item is used or not, with a constraint such as no two adjacent: house robbing, deleting to earn points.
Unbounded choices toward a target
dp over amounts, trying every coin or piece at each step: coin change, perfect squares, word break.
Ending at i
dp[i] is the best answer that ends exactly at i, and the final answer is the max over all i: maximum subarray, longest increasing subsequence.
Small state machine
Track several values per index (holding, sold, cooling down) for stock trading with rules.

Pitfalls

  • A vague state definition, which makes the recurrence impossible to check.
  • Wrong base cases, especially dp[0] for 'number of ways' (usually 1, the empty way).
  • Iterating in an order that reads entries before they are computed.
  • Using infinity sentinels and then returning them instead of the 'impossible' value.
  • Confusing combinations and permutations in counting problems: the loop order (coins outer or amounts outer) decides which you count.

What it costs

OperationCost
Linear recurrence over n statesO(n) time
Target DP with k choicesO(target × k) time, O(target) space
Rolling variablesO(1) space when dp[i] uses a fixed window
LIS with patience sortingO(n log n) instead of O(n²)
Plain recursion it replacesOften O(2^n)

49 1-D Dynamic Programming problems

Easiest first. Problems marked Free can be solved without Pro.

  1. 1Staircase PathsFreeEasy
  2. 2Toll StairwayFreeEasy
  3. 3Festival Stall SponsorFreeEasy
  4. 4Best Streak SumEasy
  5. 5One Trade ProfitEasy
  6. 6Pillar HopsEasy
  7. 7Unlimited TradesEasy
  8. 8Spaced-Out Bit StringsEasy
  9. 9Longest Climbing StreakEasy
  10. 10Fully Decodable Digit MessageEasy
  11. 11Stepping Pad ReachabilityEasy
  12. 12Distinct Digit CodesEasy
  13. 13Count Stepping Pad RoutesEasy
  14. 14Custom Stride StairsMedium
  15. 15Plaza Ring SponsorMedium
  16. 16Fewest Coins for ChangeMedium
  17. 17Coin Mix CountMedium
  18. 18Best Product RunMedium
  19. 19Harvest Without NeighboursMedium
  20. 20Digit Message DecodingsMedium
  21. 21Pillar Hops With ReachMedium
  22. 22Trades With CooldownMedium
  23. 23Trades With CommissionMedium
  24. 24Circular Track Best SegmentMedium
  25. 25Best Run With One SkipMedium
  26. 26Travel Pass PlannerMedium
  27. 27Ordered Coin SequencesMedium
  28. 28Longest Rising SubsequenceMedium
  29. 29Count Pager Message ReadingsMedium
  30. 30Glossary Segmentation CheckMedium
  31. 31Two Equal Cargo HoldsMedium
  32. 32Closest Cargo BalanceMedium
  33. 33Count Longest Rising PicksMedium
  34. 34Fewest Removals to SortedMedium
  35. 35Mirror Substring CountMedium
  36. 36Longest Mirror SubstringMedium
  37. 37Fewest Pad JumpsMedium
  38. 38Chant Syllable ChainsMedium
  39. 39Best Score With Bounded HopsMedium
  40. 40Cracked Tile CrossingMedium
  41. 41Toll Road With Long JumpsHard
  42. 42Two Disjoint RunsHard
  43. 43Limited Trade BudgetHard
  44. 44Wildcard Message DecodingsHard
  45. 45Nesting Parcel ChainHard
  46. 46Pager Readings With SmudgesHard
  47. 47Fewest Mirror CutsHard
  48. 48Accelerating Stone HopsHard
  49. 49Sparse Bit Numbers Up To NHard

Solve them in the browser, in Python or JavaScript.