1D DP Patterns and Templates
1D DP problems use a single array where dp[i] represents the optimal solution for the subproblem ending at index i.
Pattern 1: Fibonacci-like
Problems where each state depends on the previous one or two states.
// Template: dp[i] = dp[i-1] + dp[i-2]
// Example: Climbing Stairs, Fibonacci
public int solveFibonacci(int n) {
int[] dp = new int[n + 1];
dp[0] = 0;
dp[1] = 1;
for (int i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
Pattern 2: Maximum/Minimum Sum
Select elements to maximize/minimize sum with constraints.
// House Robber: Maximum sum of non-adjacent elements
// dp[i] = max value considering houses 0..i
public int rob(int[] nums) {
int n = nums.length;
if (n == 0) return 0;
if (n == 1) return nums[0];
int[] dp = new int[n];
dp[0] = nums[0];
dp[1] = Math.max(nums[0], nums[1]);
for (int i = 2; i < n; i++) {
dp[i] = Math.max(dp[i - 1], // Skip current house
dp[i - 2] + nums[i]); // Rob current house
}
return dp[n - 1];
}
// Space-optimized version
public int robOptimized(int[] nums) {
int n = nums.length;
if (n == 0) return 0;
if (n == 1) return nums[0];
int prev2 = nums[0];
int prev1 = Math.max(nums[0], nums[1]);
for (int i = 2; i < n; i++) {
int curr = Math.max(prev1, prev2 + nums[i]);
prev2 = prev1;
prev1 = curr;
}
return prev1;
}
Pattern 3: Kadane's Algorithm (Maximum Subarray)
// Maximum contiguous subarray sum
// dp[i] = max subarray sum ending at index i
public int maxSubArray(int[] nums) {
int maxSoFar = nums[0];
int maxEndingHere = nums[0];
for (int i = 1; i < nums.length; i++) {
// Either extend previous subarray or start new one
maxEndingHere = Math.max(nums[i], maxEndingHere + nums[i]);
maxSoFar = Math.max(maxSoFar, maxEndingHere);
}
return maxSoFar;
}
Pattern 4: Decode Ways
// Count number of ways to decode a string of digits
// '1' -> 'A', '2' -> 'B', ..., '26' -> 'Z'
public int numDecodings(String s) {
int n = s.length();
int[] dp = new int[n + 1];
dp[0] = 1; // Empty string has 1 way
dp[1] = s.charAt(0) != '0' ? 1 : 0;
for (int i = 2; i <= n; i++) {
// Single digit decode
if (s.charAt(i - 1) != '0') {
dp[i] += dp[i - 1];
}
// Two digit decode
int twoDigit = Integer.parseInt(s.substring(i - 2, i));
if (twoDigit >= 10 && twoDigit <= 26) {
dp[i] += dp[i - 2];
}
}
return dp[n];
}
When to Use 1D DP
- Problem has sequential structure (array/string)
- State depends on a constant number of previous states
- You need to find optimal value (max/min/count)
- Greedy doesn't work (counterexample exists)
Space Optimization Techniques
Why Optimize Space?
In many 1D DP problems, dp[i] only depends on a few previous values. We can reduce space from O(n) to O(1).
Observation
// If dp[i] = f(dp[i-1], dp[i-2], ..., dp[i-k])
// We only need to keep track of the last k values
Example: House Robber with O(1) Space
public int rob(int[] nums) {
int n = nums.length;
if (n == 0) return 0;
if (n == 1) return nums[0];
// Only need prev2 and prev1
int prev2 = nums[0];
int prev1 = Math.max(nums[0], nums[1]);
for (int i = 2; i < n; i++) {
int curr = Math.max(prev1, prev2 + nums[i]);
prev2 = prev1;
prev1 = curr;
}
return prev1;
}
Generic Pattern for Rolling Variables
// Instead of:
int[] dp = new int[n + 1];
for (int i = 0; i <= n; i++) {
dp[i] = /* ... */;
}
// Use rolling variables:
int prev2 = baseValue1;
int prev1 = baseValue2;
for (int i = k; i <= n; i++) {
int curr = /* depends on prev1, prev2, ... */;
prev2 = prev1;
prev1 = curr;
}
return prev1; // or prev2 depending on the problem
Maximum Subarray with O(1) Space
public int maxSubArray(int[] nums) {
int maxSoFar = nums[0];
int maxEndingHere = nums[0];
for (int i = 1; i < nums.length; i++) {
maxEndingHere = Math.max(nums[i], maxEndingHere + nums[i]);
maxSoFar = Math.max(maxSoFar, maxEndingHere);
}
return maxSoFar;
}
Common Mistakes in Space Optimization
- Forgetting base cases: Ensure initial values are correct
- Overwriting values: Update in correct order
- Wrong return value: Return the right variable (prev1 vs prev2)
- Not handling n < k: When n is smaller than the number of variables needed
Practice Problems
You are a robber planning to rob houses along a street. Each house has a certain amount of money. The only constraint stopping you from robbing each of them is that adjacent houses have security systems connected. If two adjacent houses were broken into on the same night, the security system alerts the police. Given an integer array nums representing the amount of money of each house, return the maximum amount you can rob without alerting the police.
Example:
Input: nums = [1,2,3,1]
Output: 4
Rob house 1 (money = 1) and house 3 (money = 3). Total = 1 + 3 = 4.
Edge Cases:
- Single house (rob it, return its value)
- Two houses (rob the one with more money)
- All houses have the same value
- Strictly increasing values (rob every other house)
You are given an integer array coins representing coins of different denominations and an integer amount representing a total amount of money. Return the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return -1.
Example:
Input: coins = [1,5,10,25], amount = 30
Output: 2
25 + 5 = 30, so 2 coins.
Edge Cases:
- amount = 0 (zero coins needed, return 0)
- No coin combination can form the amount (return -1)
- Single coin denomination that doesn't divide the amount evenly
- Coins array contains values larger than the amount (unusable for small subproblems)
A message consisting of letters is encoded as: 'A' -> 1, 'B' -> 2, ..., 'Z' -> 26. Given a string s of digits, return the number of ways to decode it.
Example:
Input: s = "226"
Output: 3
"226" can be decoded as "BZ" (2 26), "VF" (22 6), or "BBF" (2 2 6).
Edge Cases:
- Leading zero ('0' at start) — 0 ways to decode
- '10' — only 1 way (10 = J)
- '27' — only 1 way (2 and 7 separately, 27 is invalid)
- '111' — 3 ways: 1-1-1, 11-1, 1-11
- Single digit '0' — 0 ways
All houses are arranged in a circle. If you rob the first house, you cannot rob the last. Return the maximum amount you can rob.
Example:
Input: nums = [2,3,2]
Output: 3
Rob house 2 (money = 3). Cannot rob house 1 and 3 together because they are adjacent in circle.
Edge Cases:
- Single house — rob it
- Two houses — rob the larger one
- All zeros — answer is 0
- All same value — rob ceil(n/2) houses
- Strictly increasing values
You are given an array `prices` where `prices[i]` is the price of a given stock on the `ith` day. You want to maximize your profit by choosing a **single day** to buy one stock and choosing a **different day in the future** to sell that stock. Return the maximum profit you can achieve from this transaction. If you cannot achieve any profit, return `0`.
Example:
Input: prices = [7, 1, 5, 3, 6, 4]
Output: 5
Buy on day 2 (price = 1) and sell on day 5 (price = 6), profit = 6 - 1 = 5.
Edge Cases:
- Single element array (no transaction possible, return 0)
- Strictly decreasing prices (no profit possible)
- All identical prices (profit is always 0)
- Prices that increase then decrease (peak is not always the best sell point)
Quiz
1. In House Robber, what does dp[i] represent?
2. What is the state transition for House Robber?
3. What is a common mistake when implementing 1D Dynamic Programming?
Flashcards
Question
What is Kadane's Algorithm used for?
Click to reveal answer
Answer
Finding the maximum sum contiguous subarray in O(n) time. dp[i] = max(nums[i], dp[i-1] + nums[i]).
Question
When can you optimize 1D DP space from O(n) to O(1)?
Click to reveal answer
Answer
When dp[i] depends only on a constant number of previous states (e.g., dp[i-1] and dp[i-2]). Use rolling variables instead of array.
Question
1D Dynamic Programming best practices
Click to reveal answer
Answer
Follow SOLID principles, write clean code, test thoroughly, document decisions, and monitor in production.
Revision Notes
Key Takeaways
- 1. 1D DP is for sequential problems with constant-width dependency
- 2. Always define dp[i] clearly before coding
- 3. Space optimization is possible when recurrence has limited dependency
- 4. Kadane's algorithm is the classic O(n) maximum subarray solution
Interview Tips
- • Start with O(n) space solution, then optimize
- • Draw out dp table for small inputs to verify
- • Explain the state transition clearly
- • Handle edge cases: empty array, single element
Cheat Sheet
1D DP Cheat Sheet
Common Patterns:
- Fibonacci-like: dp[i] = dp[i-1] + dp[i-2]
- Max sum non-adjacent: dp[i] = max(dp[i-1], dp[i-2] + nums[i])
- Kadane's: maxEndingHere = max(nums[i], maxEndingHere + nums[i])
Space Optimization:
- If dp[i] depends on dp[i-1] and dp[i-2], use two variables
- If dp[i] depends on dp[i-1..i-k], use k variables
Template:
int prev2 = base1;
int prev1 = base2;
for (int i = k; i < n; i++) {
int curr = f(prev1, prev2, ...);
prev2 = prev1;
prev1 = curr;
}
return prev1;
Key Insight:
- Always ask: "What decision do I make at each step?"