Expert: advanced patterns and interviews · Dynamic programming · topic 53 of 61 · 40 min
1-D DP classics
Explanation
House robber: take it or skip it
Houses stand in a row with some money in each. You may not take from two neighbouring houses (the alarm links them). What is the most you can collect? Greedy ideas such as "take every other house" fail: for [2, 1, 1, 9] the best is 2 + 9 = 11.
State: dp[i] = the most money from the first i houses. For house i − 1 (the last one in that group) there are two choices. Skip it: dp[i − 1]. Take it: then its neighbour must be skipped, so nums[i − 1] + dp[i − 2]. Recurrence: dp[i] = max(dp[i − 1], dp[i − 2] + nums[i − 1]), with dp[0] = 0 and dp[1] = nums[0]. Only two values are read, so two variables are enough: O(n) time, O(1) space.
| houses so far | [] | [2] | [2, 7] | [2, 7, 9] | [2, 7, 9, 3] | [2, 7, 9, 3, 1] |
|---|---|---|---|---|---|---|
| dp | 0 | 2 | 7 | 11 | 11 | 12 |
Coin change: the fewest coins
Given coin values (unlimited coins of each) and an amount, find the fewest coins that add up to the amount, or −1 if it cannot be done. Greedy ("always take the biggest coin that fits") works for Indian rupee coins, but fails for unusual sets: with coins [1, 3, 4] and amount 6, greedy takes 4 + 1 + 1 (three coins) while 3 + 3 uses two.
State: dp[a] = the fewest coins that make amount a. The last coin used was some c, so dp[a] = min over coins c ≤ a of dp[a − c] + 1. Base case: dp[0] = 0. Amounts that cannot be made stay at infinity, and you turn infinity into −1 at the end. Time O(amount × number of coins), space O(amount).
def coin_change_min(coins, amount):
INF = float("inf")
dp = [0] + [INF] * amount
for a in range(1, amount + 1):
for c in coins:
if c <= a and dp[a - c] + 1 < dp[a]:
dp[a] = dp[a - c] + 1
return dp[amount] if dp[amount] != INF else -1
print(coin_change_min([1, 3, 4], 6)) # Output: 2
print(coin_change_min([2], 3)) # Output: -1Coin change: counting the ways (loop order matters)
Now count how many different combinations of coins make the amount. With coins [1, 2] and amount 3 there are 2 combinations: 1+1+1 and 1+2. The order of coins does not matter, so 1+2 and 2+1 are the same combination.
State: ways[a] = the number of combinations that make a, with ways[0] = 1 (the empty combination). Put the coins in the OUTER loop and the amounts in the inner loop. Each coin is then "allowed" in a fixed order, so every combination is built in exactly one way: coins of the first kind, then of the second kind, and so on. If you swap the loops (amounts outside, coins inside), every ordering is counted separately and you get 3 for the example, because 1+2 and 2+1 both count. That other answer is right for a different question ("how many ordered sequences"), so read the question carefully.
def ways_combinations(coins, amount):
ways = [1] + [0] * amount
for c in coins: # coins outside
for a in range(c, amount + 1):
ways[a] += ways[a - c]
return ways[amount]
def ways_orderings(coins, amount):
ways = [1] + [0] * amount
for a in range(1, amount + 1): # amounts outside
for c in coins:
if c <= a:
ways[a] += ways[a - c]
return ways[amount]
print(ways_combinations([1, 2], 3), ways_orderings([1, 2], 3)) # Output: 2 3Decode ways
A message of letters was turned into digits with A = 1, B = 2, … Z = 26. "226" could be "BZ" (2, 26), "VF" (22, 6) or "BBF" (2, 2, 6), so there are 3 ways to decode it. State: dp[i] = the number of ways to decode the first i characters, with dp[0] = 1. The last piece is either one digit (allowed if it is not "0") or two digits (allowed if they form 10 to 26). So dp[i] = (dp[i − 1] if s[i − 1] != "0") + (dp[i − 2] if 10 ≤ int(s[i − 2:i]) ≤ 26). Zeros are the trap: "06" has 0 ways, because "0" is not a letter and "06" is not allowed as a two-digit code.
Word break
Can the string be split into words from a dictionary? "applepenapple" with ["apple", "pen"] can; "catsandog" with ["cats", "dog", "sand", "and", "cat"] cannot. State: ok[i] = True if the first i characters can be split. ok[0] = True. ok[i] is True if some j < i has ok[j] True and s[j:i] in the word set. With n characters this checks O(n²) pairs, and each slice costs up to O(n), so O(n³) in the worst case; limiting j to the longest word length helps a lot.
def word_break(s, words):
word_set = set(words)
ok = [True] + [False] * len(s)
for i in range(1, len(s) + 1):
for j in range(i):
if ok[j] and s[j:i] in word_set:
ok[i] = True
break
return ok[len(s)]
print(word_break("applepenapple", ["apple", "pen"])) # Output: True
print(word_break("catsandog", ["cats", "dog", "sand", "and", "cat"])) # Output: FalseCommon mistakes
- House robber: assuming the answer is "every other house". Fix: write the take/skip recurrence and test [2, 1, 1, 9].
- Coin change minimum: returning infinity, or 0, when the amount cannot be made. Fix: convert the "impossible" marker to −1 at the end, and test an impossible case.
- Coin change ways: putting the loops in the wrong order and counting orderings. Fix: coins outside for combinations; test coins [1, 2] with amount 3, which must give 2.
- Decode ways: forgetting that "0" cannot stand alone, or allowing "05" as a two-digit code. Fix: the one-digit piece needs s[i − 1] != "0"; the two-digit piece needs a value from 10 to 26.
- Checking word break with a list of words instead of a set, making each lookup O(k). Fix: build a set once.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
1-D DP classics · 1/8
This lesson covers the one-dimensional DP classics that interviewers love: house robber, coin change, decode ways and word break.
Prefer a video by a teacher? Find free videos on “house robber coin change decode ways dynamic programming explained” on YouTube ↗
In real life: An ATM choosing notes
An ATM must pay out an amount with the notes it has loaded. With the usual note values, taking the largest note that fits works. But when some notes run out, the loaded set can be unusual. If a machine only has ₹200 and ₹500 notes and a customer asks for ₹600, greedy takes a ₹500 note and is stuck with ₹100, while three ₹200 notes work. A coin-change style DP finds the fewest notes, or tells the machine the amount is impossible so it can refuse at once. Real machines also track how many notes of each kind remain, which turns this into a bounded version of the problem.
def fewest_notes(notes, amount):
INF = float("inf")
dp = [0] + [INF] * amount
for a in range(1, amount + 1):
for n in notes:
if n <= a:
dp[a] = min(dp[a], dp[a - n] + 1)
return dp[amount] if dp[amount] != INF else -1
print(fewest_notes([200, 500], 600))
print(fewest_notes([200, 500], 300))Practice (0/4 done)
Write rob(nums) where nums[i] ≥ 0 is the money in house i along a street. You may not take money from two neighbouring houses. Return the largest total you can take. An empty street gives 0. O(n) time, O(1) extra space. Example: rob([2, 7, 9, 3, 1]) returns 12 (2 + 9 + 1).
Write coin_change_min(coins, amount) that returns the FEWEST coins that add up to amount, using unlimited coins of each value in coins (all positive). Return -1 if the amount cannot be made, and 0 for amount 0. Example: coin_change_min([1, 2, 5], 11) returns 3 (5 + 5 + 1).
Write coin_change_ways(coins, amount) that returns the number of different COMBINATIONS of coins (unlimited of each, all values distinct and positive) that add up to amount. Order does not matter: 1+2 and 2+1 are the same combination. amount 0 has exactly 1 combination (use no coins). Example: coin_change_ways([1, 2, 5], 5) returns 4. The starter has the loops in the wrong order.
A message of capital letters was encoded with A → "1", B → "2", …, Z → "26". Write num_decodings(s) that returns how many ways the digit string s can be decoded. "0" alone is not a letter, and a two-digit code must be between "10" and "26" (so "06" is not allowed). Return 0 for an empty string. Example: num_decodings("226") returns 3.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- 2-D DP on grids and stringsDynamic programming · same module
- Knapsack problemsDynamic programming · same module
- Loops: repeating workFrontend Web Development · JavaScript basics
- Loops: for, while, do…while, for…of and for…inJavaScript Programming (language in depth) · Strings, decisions and loops
- Composables and modifiersKotlin and Android App Development · Jetpack Compose UI
- break, continue and loop elsePython Programming · Loops: repeating work
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
For house robber, what is dp[i] built from?
Answer: max(dp[i − 1], dp[i − 2] + nums[i − 1])
Either skip the last house (dp[i − 1]) or take it and the best from two houses back.
Coins [1, 3, 4], amount 6. What does "always take the largest coin that fits" give, and what is optimal?
Answer: 3 coins and 2 coins
Greedy takes 4, 1, 1 (three coins); 3 + 3 needs only two. That is why DP is needed.
In the ways-to-make-change DP, putting the AMOUNTS in the outer loop and coins in the inner loop counts…
Answer: Ordered sequences, so 1+2 and 2+1 are counted separately
With amounts outside, every coin can be the last one at every amount, so each ordering is a separate path.
How many ways can "12" be decoded with A = 1 … Z = 26?
Answer: 2
"1 2" gives AB and "12" gives L.
What is the time of the coin-change-minimum DP with amount A and k coin values?
Answer: O(A × k)
There are A + 1 states, and each tries k coins.
Coins {1, 3, 4} and amount 6: how many coins do greedy and the best answer use?Related
You only need the LCS LENGTH of two strings of lengths m and n. What is the smallest space you need with the usual DP?Related
Answer: O(min(m, n)) using two rows
Each row reads only the row above, so two rows of the shorter length are enough. To rebuild the string you need the full table (or a cleverer method).
A DP has n × m states and does O(1) work per state. What is its time?Related
nums = [1, 2, 3, 5]. Can it be split into two groups with equal sums?Related
Answer: No, the total 11 is odd
An odd total can never be split into two equal whole-number halves.
From Knapsack problems
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- 3 more topics in “Dynamic programming” before its exam: 2-D DP on grids and strings, Knapsack problems, More DP patterns and how to spot them.
- Module 11 exam: Dynamic programming · 15 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, Interview strategy and mock problems. 61 topics left in the course before the final exam.