Step 1 Understand it
Remember it by
Write what dp[i] means in one sentence, then build it from dp[i − 1] and dp[i − 2].
Watch it happen
Read the animation as text
- How many ways to climb 5 steps, taking 1 or 2 at a time? Your last move onto step i came from step i − 1 or step i − 2 — so ways(i) = ways(i − 1) + ways(i − 2).
- Base cases: there is 1 way to stand at step 0 (do nothing) and 1 way to reach step 1 (one small step).
- dp[2] = dp[1] + dp[0] = 1 + 1 = 2. Both answers were already saved, so this costs one addition.
- dp[3] = dp[2] + dp[1] = 2 + 1 = 3. Both answers were already saved, so this costs one addition.
- dp[4] = dp[3] + dp[2] = 3 + 2 = 5. Both answers were already saved, so this costs one addition.
- dp[5] = dp[4] + dp[3] = 5 + 3 = 8. Both answers were already saved, so this costs one addition.
- Answer: 8 ways. Each step is worked out once from saved answers: O(n), instead of the O(2ⁿ) of plain recursion that keeps re-solving the same steps.
The idea
Dynamic programming solves a problem by reusing answers to smaller versions of it. In 1-D, dp[i] is the answer for the first i items (or for amount i). Find the recurrence (how dp[i] comes from earlier entries), set the base cases, and fill the table in order.
Picture it
flowchart LR A["dp[i − 2]"] --> C["dp[i]"] B["dp[i − 1]"] --> C C --> D["dp[i + 1]"]
Analogy
Climbing stairs and writing on each step how many ways there are to reach it. To fill in a new step, you just add the numbers on the one or two steps below — no recounting.
Where the analogy breaks
On stairs the earlier numbers are obviously right. In DP you must define dp[i] precisely; if your definition is vague, the recurrence will quietly be wrong.
More detail
What it is
Dynamic Programming (DP) sounds terrifying, but it's actually just one simple idea: 'Don't do the same work twice!' Imagine I ask you to calculate 1+1+1+1. You say '4'. Then I add another '+1' to the end. Do you recount everything from the start? No! You just remember your last answer (4) and add 1 to get 5. That's exactly what DP is: saving the answers to smaller sub-problems exactly once so you can instantly lookup that answer later when solving a bigger problem.
When to use it
- When a problem asks for the 'minimum', 'maximum', 'longest', or 'total ways' to do something.
- When you notice that a recursive solution calculates the exact same branches multiple times (Overlapping Subproblems).
How it works
There are two ways to do DP.\n1. **Top-Down (Memoization)**: You write a standard recursive function, but you add a HashMap or Array to 'cache' the result before returning. Next time the function is called with those same arguments, you return the cached result instantly.\n2. **Bottom-Up (Tabulation)**: You build an array (usually 1-Dimensional here) starting from index 0. You solve the smallest piece of the puzzle first, then use a for-loop to build up to the final answer.
Example walkthrough
Input: Climbing Stairs: N=3 (can take 1 or 2 steps)
- Step 0 or 1: 1 way. dp=[1, 1].
- Step 2: Can be reached from step 1 or step 0. So dp[2] = dp[1] + dp[0] = 2. dp=[1,1,2].
- Step 3: Can be reached from step 2 or step 1. So dp[3] = dp[2] + dp[1] = 2+1 = 3. dp=[1,1,2,3].
- Answer is dp[3], which is 3!
Result: 3
Related patterns
- Backtracking: DP is just backtracking with a super-powered memory cache!
Step 2 Recognise it
Words that signal it
- 'Number of ways' to do something
- 'Minimum / maximum' cost, profit or count
- Choices at each step that affect later choices (rob this house or not)
- The brute-force recursion repeats the same sub-question many times
Don't confuse it with
- Greedy: If one locally best choice can be wrong later (coins [1, 3, 4] for 6: greedy gives 4 + 1 + 1, DP gives 3 + 3), you need DP.
- Backtracking: List all the ways → backtracking. Count them or find the best → DP.
Gut check
Would the brute force ask the same sub-question again and again? → DP.
How to recognise it in an interview
The problem talks about finding 'Max/Min/Longest', 'Number of ways', or involves jumping/taking steps.
Step 3 The template
The general shape. Adapt it to each problem rather than learning one solution per problem.
def solve(nums):
n = len(nums)
dp = [0] * (n + 1) # dp[i] = answer using the first i items <- write this down!
dp[0] = ... # base case(s)
for i in range(1, n + 1):
dp[i] = ... # from dp[i - 1], dp[i - 2], ...
return dp[n]The same template in Java
class Solution {
// Bottom-Up approach for Climbing Stairs
public int climbStairs(int n) {
if (n <= 2) return n;
int[] dp = new int[n + 1];
dp[1] = 1;
dp[2] = 2;
for (int i = 3; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
}Common mistakes
- Never writing down what dp[i] means.
- Wrong base cases (dp[0] for climbing stairs is 1: one way to stand still).
- In Coin Change, using 0 instead of infinity for 'impossible'.
- Using recursion without a cache, resulting in O(2^N) time limit exceeded errors.
Step 4 Watch it run
Two problems, traced step by step exactly as the code runs.
Easy example: Climbing Stairs (1 or 2 steps at a time)
Input: n = 5
| step i | how | ways dp[i] |
|---|---|---|
| 0 | base case | 1 |
| 1 | base case | 1 |
| 2 | dp[1] + dp[0] = 1 + 1 | 2 |
| 3 | dp[2] + dp[1] = 2 + 1 | 3 |
| 4 | dp[3] + dp[2] = 3 + 2 | 5 |
| 5 | dp[4] + dp[3] = 5 + 3 | 8 |
Result: 8
Notice: To land on step i, your last move came from step i − 1 or i − 2. Add the two counts.
def climb_stairs(n):
a, b = 1, 1 # ways to reach step 0 and step 1
for _ in range(2, n + 1):
a, b = b, a + b
return bMedium example: House Robber
Input: nums = [2, 7, 9, 3, 1]
| house (money) | skip it | rob it | best so far |
|---|---|---|---|
| 0 (£2) | skip: 0 | rob: 0 + 2 = 2 | 2 |
| 1 (£7) | skip: 2 | rob: 0 + 7 = 7 | 7 |
| 2 (£9) | skip: 7 | rob: 2 + 9 = 11 | 11 |
| 3 (£3) | skip: 11 | rob: 7 + 3 = 10 | 11 |
| 4 (£1) | skip: 11 | rob: 11 + 1 = 12 | 12 |
Result: 12
Notice: At each house there are only two choices: skip it (keep the best so far) or rob it (add it to the best from two houses back).
def rob(nums):
rob1, rob2 = 0, 0 # best up to two houses back, best up to the last house
for x in nums:
rob1, rob2 = rob2, max(rob2, rob1 + x)
return rob2Step 5 Practise
Read each question and say the pattern and the trick out loud before you open the answer. Then solve it on LeetCode. Tick a problem once you've solved it on your own: it comes back for review tomorrow, then after 3, 7, 14 and 30 days, then every month, on the DSA page.
Easy
-
Climbing Stairs Easy
Count the ways to climb n stairs taking 1 or 2 steps at a time.
Show answer
Technique: 1-D DP (Fibonacci)
The clue: 'How many ways' with small steps.
The trick: ways(i) = ways(i − 1) + ways(i − 2).
-
Each step has a cost; find the cheapest way to reach the top, taking 1 or 2 steps.
Show answer
Technique: 1-D DP
The clue: 'Minimum cost' with 1-or-2 choices.
The trick: cost to reach i = cost[i] + min(the cost to reach i − 1, the cost to reach i − 2).
Medium
-
House Robber Medium
Rob houses in a row for the most money without robbing two neighbours.
Show answer
Technique: 1-D DP: rob or skip
The clue: Maximise with a 'not next to each other' rule.
The trick: best(i) = max(best(i − 1), best(i − 2) + nums[i]).
-
House Robber II Medium
House Robber, but the houses are in a circle.
Show answer
Technique: 1-D DP twice
The clue: The first and last houses are now neighbours.
The trick: Run House Robber on all houses except the last, and on all except the first; take the better.
-
Find the longest substring that is a palindrome.
Show answer
Technique: Expand around centres
The clue: Palindromes grow outwards from a middle.
The trick: Try every centre (each character and each gap) and expand while both ends match.
-
Palindromic Substrings Medium
Count the substrings that are palindromes.
Show answer
Technique: Expand around centres
The clue: Every palindrome has a centre.
The trick: Expand from each of the 2n − 1 centres, counting each successful expansion.
-
Decode Ways Medium
Count the ways to decode a digit string where 1 = A … 26 = Z.
Show answer
Technique: 1-D DP
The clue: 'How many ways', each step uses one or two digits.
The trick: ways(i) = ways(i − 1) if the digit isn't 0, plus ways(i − 2) if the two digits make 10 … 26.
-
Coin Change Medium
Find the fewest coins that make an amount (or −1).
Show answer
Technique: 1-D DP over amounts (unbounded)
The clue: 'Fewest' coins — greedy fails for some coin sets.
The trick: dp[a] = 1 + min(dp[a − c]) over coins c; start with infinity for impossible.
-
Maximum Product Subarray Medium
Find the contiguous subarray with the largest product.
Show answer
Technique: Track running max and min
The clue: Negative numbers can turn the smallest product into the largest.
The trick: Keep both the max and min product ending here; a negative number swaps them.
-
Word Break Medium
Decide whether a string can be split into dictionary words.
Show answer
Technique: 1-D DP over prefixes
The clue: 'Can it be split' with repeated sub-questions.
The trick: dp[i] is true if some dp[j] is true and s[j:i] is a dictionary word.
-
Find the length of the longest strictly increasing subsequence.
Show answer
Technique: 1-D DP (or patience sorting)
The clue: Subsequence (not contiguous) + longest.
The trick: dp[i] = 1 + max(dp[j]) for j < i with nums[j] < nums[i]; or keep 'smallest tail' values with binary search for O(n log n).
-
Partition Equal Subset Sum Medium
Decide whether the array can be split into two parts with equal sums.
Show answer
Technique: 0/1 knapsack DP
The clue: Choose some numbers to hit exactly half the total.
The trick: If the total is odd, false; else track reachable sums (a set or boolean array), each number used once.
What to say in the interview
'Since this problem exhibits overlapping subproblems, a naive recursive approach will TLE. I will use Dynamic Programming to cache previous results.'
Follow-up prep
Can you optimize the space complexity from O(N) to O(1) by simply keeping track of the last two variables?