Worked DP 1: Climbing Stairs
You can climb one or two steps at a time. Return how many distinct
ways there are to reach step n.
Hidden answer: state, recurrence, tests, and Python solution
State: ways[i] is the number of ways to reach step
i. Recurrence: the last move came from
i - 1 or i - 2. Test n = 1,
n = 2, and a larger value. The rolling variables store
the two previous states.
def climb_stairs(n):
if n <= 2:
return n
two_back = 1
one_back = 2
for _ in range(3, n + 1):
two_back, one_back = one_back, one_back + two_back
return one_back
Worked DP 2: House Robber
Given non-negative values in houses on a line, return the maximum
amount you can rob without robbing adjacent houses.
Hidden answer: invariant, common mistake, and Python solution
State: the best value after considering the prefix ending at the
current house. Invariant: prev1 is the best value for
the processed prefix and prev2 is the best value before
that. The common mistake is adding the current house to a value that
may already include the previous house.
def rob(nums):
prev2 = 0
prev1 = 0
for value in nums:
take = prev2 + value
skip = prev1
prev2, prev1 = prev1, max(take, skip)
return prev1
Worked DP 3: Coin Change
Given coin denominations and an amount, return the fewest coins
needed to make that amount, or -1 if impossible.
Hidden answer: state, edge cases, and Python solution
State: dp[a] is the fewest coins needed for amount
a. Base: dp[0] = 0. Impossible states
remain larger than any valid answer. Test amount zero, impossible
amounts, duplicate coin values, and coin systems where greedy fails.
def coin_change(coins, amount):
inf = amount + 1
dp = [inf] * (amount + 1)
dp[0] = 0
for a in range(1, amount + 1):
for coin in coins:
if coin <= a:
dp[a] = min(dp[a], dp[a - coin] + 1)
return -1 if dp[amount] == inf else dp[amount]
Worked DP 4: Longest Increasing Subsequence
Return the length of the longest strictly increasing subsequence.
Hidden answer: two valid approaches
The O(n^2) DP state is dp[i]: the best increasing
subsequence ending at i. The faster O(n log n)
approach keeps tails[k], the smallest possible tail
for a subsequence of length k + 1. The tails list is
not necessarily the final subsequence; it is a compact frontier.
from bisect import bisect_left
def length_of_lis(nums):
tails = []
for value in nums:
i = bisect_left(tails, value)
if i == len(tails):
tails.append(value)
else:
tails[i] = value
return len(tails)
Blind 75 Prompt: Decode Ways
Given a digit string where A is 1 through Z is 26, return how many
valid decodings exist.
Hidden answer: state and mistakes
State: dp[i] is the number of decodings for the prefix
s[:i]. A one-digit transition is valid for 1 through
9; a two-digit transition is valid for 10 through 26. Common
mistakes are treating 0 as decodable by itself and
forgetting that 30 is invalid.