Dynamic Programming Questions Placement
Dynamic Programming - Complete Placement Interview Question Bank
Last Updated: March 2026
Introduction: Why Dynamic Programming Matters in Coding Interviews
Dynamic Programming (DP) is the crown jewel of algorithmic problem-solving and the topic that separates good programmers from great ones. It appears in 30-35% of interviews at top-tier companies like Google, Facebook, Amazon, and Microsoft.
DP tests your ability to:
- Break down complex problems into simpler subproblems
- Recognize patterns and overlapping subproblems
- Optimize solutions from exponential to polynomial time
- Think recursively and iteratively
Mastering DP demonstrates that you can solve problems that seem impossible at first glance—a crucial skill for senior engineering roles.
Key Concepts and Theory
1. What is Dynamic Programming?
Dynamic Programming is an optimization technique for solving problems by breaking them into smaller subproblems and storing their solutions to avoid redundant computation.
Key Requirements:
- Optimal Substructure: Optimal solution can be constructed from optimal solutions to subproblems
- Overlapping Subproblems: Same subproblems are solved multiple times
2. DP Approaches
TOP-DOWN (Memoization):
├── Start with original problem
├── Break into subproblems recursively
├── Store solutions in memo/cache
└── Check memo before computing
BOTTOM-UP (Tabulation):
├── Start with smallest subproblems
├── Fill table iteratively
├── Build up to original problem
└── Often more space efficient
3. The DP Framework
STEP 1: Define State
- What does dp[i] or dp[i][j] represent?
STEP 2: Formulate Recurrence
- How to compute dp[i] from previous states?
STEP 3: Initialize Base Cases
- What are the simplest subproblems?
STEP 4: Determine Order
- What order to fill the table?
STEP 5: Extract Answer
- Where is the final answer stored?
Time/Space Complexity Cheat Sheet
┌─────────────────────────────────────────────────────────────┐
│ DP COMPLEXITY PATTERNS │
├─────────────────────────────────────────────────────────────┤
│ 1D DP with O(1) transition │ O(n) time, O(n) space │
│ 1D DP space optimized │ O(n) time, O(1) space │
│ 2D DP (grid/matrix) │ O(n²) time, O(n²) space │
│ 2D DP space optimized │ O(n²) time, O(n) space │
│ Knapsack (items × capacity) │ O(n×W) time, O(n×W) space │
│ Knapsack space optimized │ O(n×W) time, O(W) space │
│ Interval DP │ O(n²) or O(n³) time │
│ Bitmask DP │ O(2^n × n) time │
│ Tree DP │ O(n) time, O(h) space │
└─────────────────────────────────────────────────────────────┘
DP Patterns
Pattern 1: Fibonacci Style
- Problems: Climbing Stairs, House Robber, Jump Game
- Recurrence:
dp[i] = dp[i-1] + dp[i-2](or similar)
Pattern 2: 0/1 Knapsack
- Problems: Subset Sum, Partition Equal Subset Sum, Target Sum
- Recurrence:
dp[i][w] = max(dp[i-1][w], val[i] + dp[i-1][w-wt[i]])
Pattern 3: Unbounded Knapsack
- Problems: Coin Change, Coin Change II, Perfect Squares
- Recurrence:
dp[w] = min/max(dp[w], dp[w-coin] + 1)
Pattern 4: Longest Common Subsequence (LCS)
- Problems: LCS, Edit Distance, Longest Palindromic Subsequence
- Recurrence:
dp[i][j] = dp[i-1][j-1] + 1if match, elsemax(dp[i-1][j], dp[i][j-1])
Pattern 5: Palindromes
- Problems: Longest Palindromic Substring, Palindromic Partitioning
- Recurrence: Expand around center or
dp[i][j] = dp[i+1][j-1] && s[i]==s[j]
Pattern 6: Decision Making
- Problems: House Robber II, Best Time to Buy/Sell Stock, Jump Game
- Recurrence: Choose best among options at each step
Practice Questions with Solutions
Question 1: Climbing Stairs (EASY ⭐)
Problem: Count ways to climb n stairs, taking 1 or 2 steps at a time.
Solution:
def climb_stairs(n):
"""
Fibonacci pattern - O(n) time, O(1) space
dp[i] = ways to reach step i
dp[i] = dp[i-1] + dp[i-2]
"""
if n <= 2:
return n
# Space optimized - only need last two values
prev2, prev1 = 1, 2
for i in range(3, n + 1):
current = prev1 + prev2
prev2 = prev1
prev1 = current
return prev1
# Full DP table version
def climb_stairs_dp(n):
if n <= 2:
return n
dp = [0] * (n + 1)
dp[1], dp[2] = 1, 2
for i in range(3, n + 1):
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
Question 2: House Robber (MEDIUM ⭐⭐)
Problem: Max money from houses without robbing adjacent houses.
Solution:
def rob(nums):
"""
Decision making DP - O(n) time, O(1) space
dp[i] = max money up to house i
dp[i] = max(dp[i-1], nums[i] + dp[i-2])
"""
if not nums:
return 0
if len(nums) <= 2:
return max(nums)
prev2, prev1 = nums[0], max(nums[0], nums[1])
for i in range(2, len(nums)):
current = max(prev1, nums[i] + prev2)
prev2 = prev1
prev1 = current
return prev1
# With DP array (easier to understand)
def rob_dp(nums):
if not nums:
return 0
n = len(nums)
dp = [0] * (n + 1)
dp[1] = nums[0]
for i in range(2, n + 1):
dp[i] = max(dp[i-1], nums[i-1] + dp[i-2])
return dp[n]
Question 3: Coin Change (MEDIUM ⭐⭐)
Problem: Minimum coins needed to make amount (infinite supply).
Solution:
def coin_change(coins, amount):
"""
Unbounded knapsack - O(n × amount) time, O(amount) space
dp[i] = min coins to make amount i
dp[i] = min(dp[i], dp[i-coin] + 1) for all coins
"""
# Initialize with infinity
dp = [float('inf')] * (amount + 1)
dp[0] = 0 # 0 coins needed for amount 0
for i in range(1, amount + 1):
for coin in coins:
if coin <= i:
dp[i] = min(dp[i], dp[i - coin] + 1)
return dp[amount] if dp[amount] != float('inf') else -1
# Alternative: iterate coins first
def coin_change_alternative(coins, amount):
dp = [float('inf')] * (amount + 1)
dp[0] = 0
for coin in coins:
for i in range(coin, amount + 1):
dp[i] = min(dp[i], dp[i - coin] + 1)
return dp[amount] if dp[amount] != float('inf') else -1
Question 4: Longest Increasing Subsequence (MEDIUM ⭐⭐)
Problem: Find length of longest strictly increasing subsequence.
Solution:
def length_of_lis(nums):
"""
DP with binary search optimization - O(n log n) time, O(n) space
tails[i] = smallest tail of increasing subsequence of length i+1
"""
tails = []
for num in nums:
# Binary search for position to replace
left, right = 0, len(tails)
while left < right:
mid = (left + right) // 2
if tails[mid] < num:
left = mid + 1
else:
right = mid
if left == len(tails):
tails.append(num) # Extend longest subsequence
else:
tails[left] = num # Replace to allow more future growth
return len(tails)
# Classic O(n²) DP
def length_of_lis_dp(nums):
if not nums:
return 0
n = len(nums)
dp = [1] * n # Each element is LIS of length 1
for i in range(1, n):
for j in range(i):
if nums[j] < nums[i]:
dp[i] = max(dp[i], dp[j] + 1)
return max(dp)
Question 5: Longest Common Subsequence (MEDIUM ⭐⭐)
Problem: Find length of longest subsequence common to both strings.
Solution:
def longest_common_subsequence(text1, text2):
"""
Classic 2D DP - O(m × n) time, O(min(m,n)) space
dp[i][j] = LCS of text1[0:i] and text2[0:j]
"""
m, n = len(text1), len(text2)
# Space optimized - only need previous row
prev = [0] * (n + 1)
curr = [0] * (n + 1)
for i in range(1, m + 1):
for j in range(1, n + 1):
if text1[i-1] == text2[j-1]:
curr[j] = prev[j-1] + 1
else:
curr[j] = max(prev[j], curr[j-1])
prev, curr = curr, prev # Swap rows
return prev[n]
# Full DP table with reconstruction
def lcs_with_reconstruction(text1, text2):
m, n = len(text1), len(text2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
# Fill table
for i in range(1, m + 1):
for j in range(1, n + 1):
if text1[i-1] == text2[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
# Reconstruct LCS
lcs = []
i, j = m, n
while i > 0 and j > 0:
if text1[i-1] == text2[j-1]:
lcs.append(text1[i-1])
i -= 1
j -= 1
elif dp[i-1][j] > dp[i][j-1]:
i -= 1
else:
j -= 1
return ''.join(reversed(lcs))
Question 6: Edit Distance (HARD ⭐⭐⭐)
Problem: Minimum operations (insert, delete, replace) to convert word1 to word2.
Solution:
def min_distance(word1, word2):
"""
2D DP - O(m × n) time, O(min(m,n)) space
dp[i][j] = edit distance between word1[0:i] and word2[0:j]
"""
m, n = len(word1), len(word2)
# Space optimized
prev = list(range(n + 1)) # dp[0][j] = j (insert j chars)
curr = [0] * (n + 1)
for i in range(1, m + 1):
curr[0] = i # dp[i][0] = i (delete i chars)
for j in range(1, n + 1):
if word1[i-1] == word2[j-1]:
curr[j] = prev[j-1] # No operation needed
else:
# min(insert, delete, replace)
curr[j] = 1 + min(curr[j-1], # Insert
prev[j], # Delete
prev[j-1]) # Replace
prev, curr = curr, prev
return prev[n]
Question 7: 0/1 Knapsack (MEDIUM ⭐⭐)
Problem: Maximize value with weight constraint (each item used at most once).
Solution:
def knapsack(weights, values, capacity):
"""
0/1 Knapsack - O(n × capacity) time, O(capacity) space
dp[w] = max value achievable with capacity w
"""
n = len(weights)
dp = [0] * (capacity + 1)
for i in range(n):
# Iterate backwards to prevent reuse
for w in range(capacity, weights[i] - 1, -1):
dp[w] = max(dp[w], values[i] + dp[w - weights[i]])
return dp[capacity]
# With item tracking
def knapsack_with_items(weights, values, capacity):
n = len(weights)
dp = [[0] * (capacity + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
for w in range(capacity + 1):
if weights[i-1] <= w:
dp[i][w] = max(dp[i-1][w],
values[i-1] + dp[i-1][w - weights[i-1]])
else:
dp[i][w] = dp[i-1][w]
# Backtrack to find items
items = []
w = capacity
for i in range(n, 0, -1):
if dp[i][w] != dp[i-1][w]:
items.append(i - 1)
w -= weights[i-1]
return dp[n][capacity], items
Question 8: Partition Equal Subset Sum (MEDIUM ⭐⭐)
Problem: Determine if array can be partitioned into two subsets with equal sum.
Solution:
def can_partition(nums):
"""
0/1 Knapsack variation - O(n × sum) time, O(sum) space
Goal: Find subset with sum = total_sum / 2
"""
total = sum(nums)
# Odd sum can't be partitioned equally
if total % 2 != 0:
return False
target = total // 2
dp = [False] * (target + 1)
dp[0] = True
for num in nums:
# Backwards to prevent reuse
for i in range(target, num - 1, -1):
dp[i] = dp[i] or dp[i - num]
return dp[target]
Question 9: Target Sum (MEDIUM ⭐⭐)
Problem: Assign + or - to each number to reach target sum.
Solution:
def find_target_sum_ways(nums, target):
"""
Transform to subset sum - O(n × sum) time, O(sum) space
Key: P - N = target, P + N = sum
2P = target + sum
P = (target + sum) / 2
"""
total = sum(nums)
# Check if possible
if abs(target) > total or (target + total) % 2 != 0:
return 0
subset_sum = (target + total) // 2
dp = [0] * (subset_sum + 1)
dp[0] = 1 # One way to make sum 0
for num in nums:
for i in range(subset_sum, num - 1, -1):
dp[i] += dp[i - num]
return dp[subset_sum]
Question 10: Longest Palindromic Substring (MEDIUM ⭐⭐)
Problem: Find longest palindromic substring.
Solution:
def longest_palindrome(s):
"""
Expand around center - O(n²) time, O(1) space
Better than DP for this problem
"""
if not s:
return ""
start = end = 0
for i in range(len(s)):
# Odd length palindromes
len1 = expand_around_center(s, i, i)
# Even length palindromes
len2 = expand_around_center(s, i, i + 1)
max_len = max(len1, len2)
if max_len > end - start:
start = i - (max_len - 1) // 2
end = i + max_len // 2
return s[start:end + 1]
def expand_around_center(s, left, right):
while left >= 0 and right < len(s) and s[left] == s[right]:
left -= 1
right += 1
return right - left - 1
# DP approach
def longest_palindrome_dp(s):
"""
DP - O(n²) time, O(n²) space
"""
n = len(s)
if n < 2:
return s
# dp[i][j] = s[i:j+1] is palindrome
dp = [[False] * n for _ in range(n)]
start = 0
max_len = 1
# Single characters are palindromes
for i in range(n):
dp[i][i] = True
# Check substrings
for length in range(2, n + 1):
for i in range(n - length + 1):
j = i + length - 1
if length == 2:
dp[i][j] = (s[i] == s[j])
else:
dp[i][j] = (s[i] == s[j]) and dp[i+1][j-1]
if dp[i][j] and length > max_len:
start = i
max_len = length
return s[start:start + max_len]
Question 11: Unique Paths (MEDIUM ⭐⭐)
Problem: Count unique paths from top-left to bottom-right in grid.
Solution:
def unique_paths(m, n):
"""
Grid DP - O(m × n) time, O(n) space
dp[i][j] = paths to reach (i, j)
dp[i][j] = dp[i-1][j] + dp[i][j-1]
"""
# Space optimized - only need previous row
dp = [1] * n # First row: all 1s
for i in range(1, m):
for j in range(1, n):
dp[j] += dp[j-1]
return dp[-1]
# Math solution - O(min(m,n)) time
def unique_paths_math(m, n):
"""
C(m+n-2, m-1) = (m+n-2)! / ((m-1)! × (n-1)!)
"""
from math import comb
return comb(m + n - 2, min(m - 1, n - 1))
Question 12: Minimum Path Sum (MEDIUM ⭐⭐)
Problem: Find path from top-left to bottom-right with minimum sum.
Solution:
def min_path_sum(grid):
"""
Grid DP - O(m × n) time, O(n) space
dp[i][j] = min path sum to reach (i, j)
"""
if not grid or not grid[0]:
return 0
m, n = len(grid), len(grid[0])
dp = [0] * n
for i in range(m):
for j in range(n):
if i == 0 and j == 0:
dp[j] = grid[i][j]
elif i == 0:
dp[j] = dp[j-1] + grid[i][j]
elif j == 0:
dp[j] = dp[j] + grid[i][j]
else:
dp[j] = min(dp[j], dp[j-1]) + grid[i][j]
return dp[-1]
Question 13: Decode Ways (MEDIUM ⭐⭐)
Problem: Count ways to decode digits to letters (A=1, B=2, ..., Z=26).
Solution:
def num_decodings(s):
"""
DP - O(n) time, O(1) space
dp[i] = ways to decode s[0:i]
"""
if not s or s[0] == '0':
return 0
n = len(s)
# dp[i-2], dp[i-1], dp[i]
prev2, prev1 = 1, 1
for i in range(1, n):
current = 0
# Single digit (if not '0')
if s[i] != '0':
current += prev1
# Two digits (10-26)
two_digit = int(s[i-1:i+1])
if 10 <= two_digit <= 26:
current += prev2
prev2, prev1 = prev1, current
return prev1
Question 14: Word Break (MEDIUM ⭐⭐)
Problem: Determine if string can be segmented into dictionary words.
Solution:
def word_break(s, word_dict):
"""
DP - O(n² × m) time, O(n) space
n = len(s), m = average word length
dp[i] = s[0:i] can be segmented
"""
word_set = set(word_dict)
n = len(s)
dp = [False] * (n + 1)
dp[0] = True # Empty string
for i in range(1, n + 1):
for j in range(i):
if dp[j] and s[j:i] in word_set:
dp[i] = True
break
return dp[n]
# Optimized with max word length
def word_break_optimized(s, word_dict):
word_set = set(word_dict)
max_len = max(len(w) for w in word_dict) if word_dict else 0
n = len(s)
dp = [False] * (n + 1)
dp[0] = True
for i in range(1, n + 1):
for j in range(max(0, i - max_len), i):
if dp[j] and s[j:i] in word_set:
dp[i] = True
break
return dp[n]
Question 15: Maximum Subarray (Kadane's) (EASY ⭐)
Problem: Find contiguous subarray with largest sum.
Solution:
def max_subarray(nums):
"""
Kadane's Algorithm - O(n) time, O(1) space
dp[i] = max subarray sum ending at i
"""
if not nums:
return 0
max_ending_here = max_so_far = nums[0]
for num in nums[1:]:
max_ending_here = max(num, max_ending_here + num)
max_so_far = max(max_so_far, max_ending_here)
return max_so_far
# With start and end indices
def max_subarray_indices(nums):
if not nums:
return 0, 0, 0
max_sum = curr_sum = nums[0]
start = end = temp_start = 0
for i in range(1, len(nums)):
if curr_sum + nums[i] < nums[i]:
curr_sum = nums[i]
temp_start = i
else:
curr_sum += nums[i]
if curr_sum > max_sum:
max_sum = curr_sum
start = temp_start
end = i
return max_sum, start, end
Question 16: Best Time to Buy/Sell Stock with Cooldown (MEDIUM ⭐⭐)
Problem: Max profit with 1-day cooldown after selling.
Solution:
def max_profit(prices):
"""
State machine DP - O(n) time, O(1) space
States:
- hold: max profit holding stock
- sold: max profit just sold
- rest: max profit in cooldown/rest
"""
if not prices:
return 0
n = len(prices)
hold, sold, rest = -prices[0], 0, 0
for i in range(1, n):
prev_sold = sold
# Update states
hold = max(hold, rest - prices[i]) # Buy or keep holding
sold = hold + prices[i] # Sell
rest = max(rest, prev_sold) # Cooldown or stay resting
return max(sold, rest)
Question 17: Regular Expression Matching (HARD ⭐⭐⭐)
Problem: Implement regex matching with '.' and '*' support.
Solution:
def is_match(s, p):
"""
2D DP - O(m × n) time, O(n) space
dp[i][j] = s[0:i] matches p[0:j]
"""
m, n = len(s), len(p)
dp = [False] * (n + 1)
dp[0] = True # Empty matches empty
# Handle patterns like a*, a*b*, etc.
for j in range(2, n + 1):
dp[j] = dp[j-2] and p[j-1] == '*'
for i in range(1, m + 1):
new_dp = [False] * (n + 1)
for j in range(1, n + 1):
if p[j-1] == '*':
# Match zero or more of previous char
new_dp[j] = new_dp[j-2] # Zero occurrences
if p[j-2] == '.' or p[j-2] == s[i-1]:
new_dp[j] = new_dp[j] or dp[j] # One or more
elif p[j-1] == '.' or p[j-1] == s[i-1]:
new_dp[j] = dp[j-1]
dp = new_dp
return dp[n]
Question 18: Burst Balloons (HARD ⭐⭐⭐)
Problem: Burst balloons to maximize coins (interval DP).
Solution:
def max_coins(nums):
"""
Interval DP - O(n³) time, O(n²) space
dp[i][j] = max coins from bursting balloons in (i, j) exclusive
"""
# Add virtual balloons with value 1 at boundaries
nums = [1] + nums + [1]
n = len(nums)
dp = [[0] * n for _ in range(n)]
# Length of interval
for length in range(2, n):
for left in range(n - length):
right = left + length
# Try bursting each balloon last
for i in range(left + 1, right):
coins = nums[left] * nums[i] * nums[right]
coins += dp[left][i] + dp[i][right]
dp[left][right] = max(dp[left][right], coins)
return dp[0][n-1]
Question 19: Distinct Subsequences (HARD ⭐⭐⭐)
Problem: Count distinct subsequences of s that equal t.
Solution:
def num_distinct(s, t):
"""
2D DP - O(m × n) time, O(n) space
dp[i][j] = count of distinct subsequences of s[0:i] equal to t[0:j]
"""
m, n = len(s), len(t)
if n > m:
return 0
# dp[j] = count for t[0:j]
dp = [0] * (n + 1)
dp[0] = 1 # Empty string has one subsequence
for i in range(1, m + 1):
# Traverse backwards to avoid using updated values
for j in range(min(i, n), 0, -1):
if s[i-1] == t[j-1]:
dp[j] += dp[j-1]
return dp[n]
Question 20: Palindromic Partitioning II (HARD ⭐⭐⭐)
Problem: Min cuts needed for palindrome partitioning.
Solution:
def min_cut(s):
"""
DP - O(n²) time, O(n²) space
dp[i] = min cuts for s[0:i+1]
pal[i][j] = s[i:j+1] is palindrome
"""
n = len(s)
# Precompute palindromes
pal = [[False] * n for _ in range(n)]
for i in range(n):
pal[i][i] = True
for length in range(2, n + 1):
for i in range(n - length + 1):
j = i + length - 1
if length == 2:
pal[i][j] = (s[i] == s[j])
else:
pal[i][j] = (s[i] == s[j]) and pal[i+1][j-1]
# Min cuts DP
dp = [float('inf')] * n
for i in range(n):
if pal[0][i]:
dp[i] = 0
else:
for j in range(i):
if pal[j+1][i]:
dp[i] = min(dp[i], dp[j] + 1)
return dp[n-1]
# Optimized single DP
def min_cut_optimized(s):
n = len(s)
dp = [i for i in range(n)] # Max cuts = i
for center in range(n):
# Odd length
left = right = center
while left >= 0 and right < n and s[left] == s[right]:
new_cut = 0 if left == 0 else dp[left-1] + 1
dp[right] = min(dp[right], new_cut)
left -= 1
right += 1
# Even length
left, right = center, center + 1
while left >= 0 and right < n and s[left] == s[right]:
new_cut = 0 if left == 0 else dp[left-1] + 1
dp[right] = min(dp[right], new_cut)
left -= 1
right += 1
return dp[n-1]
Common Interview Follow-up Questions
-
"Can you optimize the space?"
- Most DP solutions can be space-optimized
- Rolling array technique for 2D → 1D
- Only keep necessary previous states
-
"What if we need to return the actual solution, not just the value?"
- Backtrack through DP table
- Store choices during DP computation
- Reconstruct path from stored information
-
"How would you solve this with memoization instead?"
- Top-down recursive approach
- Hash map for memo instead of array
- Often more intuitive but may have recursion overhead
-
"Can this be parallelized?"
- Grid DP: process anti-diagonals in parallel
- Some problems have inherent dependencies preventing parallelism
-
"What about negative numbers/constraints?"
- Shift indices to handle negative
- Use hash map DP for sparse states
- Check for integer overflow
Companies That Frequently Ask DP
| Company | Frequency | Common Questions |
|---|---|---|
| ⭐⭐⭐⭐⭐ | Edit Distance, Regex Matching, Burst Balloons | |
| Amazon | ⭐⭐⭐⭐⭐ | Coin Change, LIS, Word Break, House Robber |
| Microsoft | ⭐⭐⭐⭐ | LCS, Knapsack, Unique Paths, Decode Ways |
| Meta | ⭐⭐⭐⭐ | Target Sum, Palindromic Partitioning, LCS |
| Apple | ⭐⭐⭐ | Climbing Stairs, Maximum Subarray, Stock Problems |
| Netflix | ⭐⭐⭐ | Interval DP, Sequence alignment problems |
| Uber | ⭐⭐⭐ | Path finding, Minimum cost problems |
| Adobe | ⭐⭐⭐ | String DP, Palindrome problems |
Preparation Tips for DP Problems
-
Master the Framework: State → Recurrence → Base Case → Order → Answer
-
Start with Recursive: Write recursive solution first, then add memoization, then convert to iterative
-
Draw the Table: For 2D DP, draw the table and fill a few cells manually to understand the pattern
-
Recognize Patterns: Knapsack, LCS, Palindromes, Grid paths—each has a recognizable structure
-
Practice Space Optimization: Know how to reduce from 2D to 1D when possible
-
Edge Cases: Empty inputs, single elements, all same values, negative numbers
-
Time Yourself: DP problems often take longer—aim to solve medium problems in 20-25 minutes
Frequently Asked Questions (FAQ)
Q1: How do I know if a problem can be solved with DP?
A: Check for:
- Optimal substructure: Optimal solution contains optimal sub-solutions
- Overlapping subproblems: Same subproblems solved multiple times
- Counting/optimization: Problems asking for "maximum", "minimum", "count", "ways"
Q2: Top-down vs Bottom-up—which should I use?
A:
- Top-down (memoization): More intuitive, easier to write, only computes needed states
- Bottom-up (tabulation): No recursion overhead, often more space-efficient, guaranteed no stack overflow
- Interview tip: Start with top-down, mention bottom-up optimization
Q3: How do I optimize space in DP?
A:
- Rolling array: If dp[i] only depends on dp[i-1], keep only 2 rows
- In-place updates: Some grid problems allow modifying input
- State compression: Use bitmasks or hash maps for sparse states
Q4: What's the difference between greedy and DP?
A: Greedy makes locally optimal choices hoping for global optimum (faster, doesn't always work). DP explores all possibilities and chooses best (slower, always optimal). When greedy fails, use DP.
Q5: How do I handle DP with large constraints?
A:
- Matrix exponentiation: For linear recurrences (O(log n))
- Monotonic queue optimization: For sliding window DP
- Convex hull trick: For DP with special structure
- Divide and conquer optimization: For certain DP forms
Quick Reference: DP Patterns
┌────────────────────────────────────────────────────────────────┐
│ PATTERN │ STATE DEFINITION │
├────────────────────────────────────────────────────────────────┤
│ Fibonacci │ dp[i] = dp[i-1] + dp[i-2] │
│ 0/1 Knapsack │ dp[i][w] = max with/without item i │
│ Unbounded Knapsack │ dp[w] = min/max using any item │
│ LCS │ dp[i][j] = longest of s1[0:i], s2[0:j] │
│ Grid Path │ dp[i][j] = paths/sum to reach (i,j) │
│ Interval DP │ dp[i][j] = optimal for interval [i,j] │
│ State Machine │ Multiple states with transitions │
│ Tree DP │ dp[node] = optimal for subtree │
│ Bitmask DP │ dp[mask] = optimal for subset mask │
└────────────────────────────────────────────────────────────────┘
Dynamic Programming: Where subproblems meet solutions! 🧩