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
- 1Define dp[i] in one precise sentence.
- 2Write the recurrence from the last choice made at i.
- 3Set the base cases and the iteration order so dependencies are ready.
- 4Decide where the answer lives (dp[n], or the max over all i).
- 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
| Operation | Cost |
|---|---|
| Linear recurrence over n states | O(n) time |
| Target DP with k choices | O(target × k) time, O(target) space |
| Rolling variables | O(1) space when dp[i] uses a fixed window |
| LIS with patience sorting | O(n log n) instead of O(n²) |
| Plain recursion it replaces | Often O(2^n) |
49 1-D Dynamic Programming problems
Easiest first. Problems marked Free can be solved without Pro.
- 1Staircase PathsFreeEasy
- 2Toll StairwayFreeEasy
- 3Festival Stall SponsorFreeEasy
- 4Best Streak SumEasy
- 5One Trade ProfitEasy
- 6Pillar HopsEasy
- 7Unlimited TradesEasy
- 8Spaced-Out Bit StringsEasy
- 9Longest Climbing StreakEasy
- 10Fully Decodable Digit MessageEasy
- 11Stepping Pad ReachabilityEasy
- 12Distinct Digit CodesEasy
- 13Count Stepping Pad RoutesEasy
- 14Custom Stride StairsMedium
- 15Plaza Ring SponsorMedium
- 16Fewest Coins for ChangeMedium
- 17Coin Mix CountMedium
- 18Best Product RunMedium
- 19Harvest Without NeighboursMedium
- 20Digit Message DecodingsMedium
- 21Pillar Hops With ReachMedium
- 22Trades With CooldownMedium
- 23Trades With CommissionMedium
- 24Circular Track Best SegmentMedium
- 25Best Run With One SkipMedium
- 26Travel Pass PlannerMedium
- 27Ordered Coin SequencesMedium
- 28Longest Rising SubsequenceMedium
- 29Count Pager Message ReadingsMedium
- 30Glossary Segmentation CheckMedium
- 31Two Equal Cargo HoldsMedium
- 32Closest Cargo BalanceMedium
- 33Count Longest Rising PicksMedium
- 34Fewest Removals to SortedMedium
- 35Mirror Substring CountMedium
- 36Longest Mirror SubstringMedium
- 37Fewest Pad JumpsMedium
- 38Chant Syllable ChainsMedium
- 39Best Score With Bounded HopsMedium
- 40Cracked Tile CrossingMedium
- 41Toll Road With Long JumpsHard
- 42Two Disjoint RunsHard
- 43Limited Trade BudgetHard
- 44Wildcard Message DecodingsHard
- 45Nesting Parcel ChainHard
- 46Pager Readings With SmudgesHard
- 47Fewest Mirror CutsHard
- 48Accelerating Stone HopsHard
- 49Sparse Bit Numbers Up To NHard
Solve them in the browser, in Python or JavaScript.