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.
Optimal Solution — O(n) time, O(1) space
1D DP with space optimization. dp[i] = max value considering houses 0..i. At each house, choose to skip or rob.
class Solution {
public int rob(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;
}
}Edge Cases:
- Single house: return its value
- Two houses: return the larger value
- All zeros: return 0
- Decreasing values: skip middle houses
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.
Optimal Solution — O(amount × coins.length) time, O(amount) space
Bottom-up DP. dp[i] = minimum coins to make amount i.
class Solution {
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount + 1];
Arrays.fill(dp, amount + 1);
dp[0] = 0;
for (int i = 1; i <= amount; i++) {
for (int coin : coins) {
if (coin <= i) {
dp[i] = Math.min(dp[i], dp[i - coin] + 1);
}
}
}
return dp[amount] > amount ? -1 : dp[amount];
}
}Edge Cases:
- amount = 0: return 0
- No coins: return -1
- Impossible to make amount: return -1
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).
Optimal Solution — O(n) time, O(1) space
DP similar to climbing stairs. dp[i] = number of ways to decode s[0..i-1].
class Solution {
public int numDecodings(String s) {
if (s.charAt(0) == '0') return 0;
int n = s.length();
int prev2 = 1; // dp[0]
int prev1 = 1; // dp[1]
for (int i = 2; i <= n; i++) {
int curr = 0;
int oneDigit = s.charAt(i - 1) - '0';
int twoDigit = Integer.parseInt(s.substring(i - 2, i));
if (oneDigit >= 1) curr += prev1;
if (twoDigit >= 10 && twoDigit <= 26) curr += prev2;
prev2 = prev1;
prev1 = curr;
}
return prev1;
}
}Edge Cases:
- Starts with '0': return 0
- Single digit: 1 way if not '0'
- '10': 1 way
- '27': 1 way (only '2','7')
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.
Optimal Solution — O(n) time, O(1) space
Two cases: rob houses [0..n-2] or [1..n-1]. Take max.
class Solution {
public int rob(int[] nums) {
int n = nums.length;
if (n == 1) return nums[0];
return Math.max(robRange(nums, 0, n - 2), robRange(nums, 1, n - 1));
}
private int robRange(int[] nums, int start, int end) {
int prev2 = 0, prev1 = 0;
for (int i = start; i <= end; i++) {
int curr = Math.max(prev1, prev2 + nums[i]);
prev2 = prev1;
prev1 = curr;
}
return prev1;
}
}Edge Cases:
- Single house: return its value
- Two houses: return max of both
- All zeros: return 0
Quiz
1. In House Robber, what does dp[i] represent?
2. What is the state transition for House Robber?
3. What is the primary purpose of 1D Dynamic Programming?
4. 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
What is 1D Dynamic Programming?
Click to reveal answer
Answer
1D Dynamic Programming is a key concept in software engineering.
Question
When to use 1D Dynamic Programming?
Click to reveal answer
Answer
Use 1D Dynamic Programming when building production systems that require reliability, scalability, and maintainability.
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?"