Home / Coding / Dynamic Programming

Dynamic Programming

DP solves problems by breaking them into overlapping subproblems, solving each subproblem once, and storing the result to avoid recomputation.

  • Replaces exponential brute-force recursion with polynomial time by caching subproblem results (memoization) or filling a table iteratively (tabulation)
  • Two styles: top-down memoization adds caching to a recursive solution, bottom-up tabulation fills a dp array from base cases upward
  • Reach for it when the problem asks for count of ways, optimal value, or feasibility, and each choice affects what future choices are available

Quick Revision - Must Do Problems

If short on time, solve only these. They cover ~90% of DP patterns.

# Problem Pattern Why It's Essential Time to Solve
1 Climbing Stairs [B75-70] Linear 1D DP Fibonacci base — simplest DP, warm-up for every session 5 min
2 Jump Game [B75-55] Greedy / Linear DP Teaches reachability tracking — common DP/greedy overlap 10 min
3 House Robber [B75-198] Linear 1D DP Classic include/exclude at each index — foundation of all rob problems 10 min
4 House Robber II [B75-213] Linear 1D DP Circular constraint — teaches two-pass trick 15 min
5 Unique Paths [B75-62] 2D Grid DP Simplest 2D DP — every cell = sum of paths from above and left 10 min
6 Coin Change [B75-322] Unbounded Knapsack Most common unbounded knapsack — infinite supply, minimize count 15 min
7 Decode Ways [B75-91] Linear String DP Tricky 1-digit and 2-digit decode choices with leading-zero edge cases 20 min
8 Word Break [B75-139] String DP Reachability DP with substring dict lookup 20 min
9 Partition Equal Subset Sum 0/1 Knapsack Key knapsack pattern — backward iteration prevents reuse 20 min
10 Longest Increasing Subsequence [B75-300] Sequence DP Look-back pattern — dp[i] = best ending at i 20 min
11 Combination Sum IV [B75-377] Unbounded Count DP Count permutations summing to target — outer loop on target 15 min
12 Longest Common Subsequence [B75-1143] 2D String DP Foundation of edit distance and diff algorithms 20 min

Practice Tips

How to Identify a DP Problem

  • Problem asks for "number of ways", "minimum cost", "maximum value", or "is it possible"
  • Choices at one step affect or constrain future choices (optimal substructure)
  • Brute force is exponential but the same subproblems recur (overlapping subproblems)
  • Keywords: "count", "minimum", "maximum", "can you reach", "how many distinct"

DP vs. Other Techniques

If you see... Use... Not...
Count ways / feasibility, choices affect future DP Greedy (greedy commits without full exploration)
Subset selection with a capacity DP (knapsack) Backtracking (too slow for large inputs)
Sorted array, optimal subsequence length DP (sequence) Two Pointers (requires fixed window structure)
Reachability in grid or sequence DP or BFS BFS only if each step has equal cost

Interview Approach

  1. Define state: what does dp[i] or dp[i][j] represent in plain English?
  2. Write the recurrence: how does dp[i] depend on smaller subproblems?
  3. Set base cases: what are dp[0] and dp[1] (or dp[0][j] and dp[i][0])?
  4. Choose fill order: left-to-right (forward) or right-to-left (backward for 0/1 knapsack)?
  5. Identify the answer: is it dp[n], dp[n-1], max over all dp[i], or something else?

Formula Cheatsheet

Pattern dp[i] means Recurrence Base cases
Linear 1D (count ways) ways to reach position i dp[i-1] + dp[i-2] dp[0]=1, dp[1]=1
Linear 1D (min cost) min cost to reach position i min(dp[i-1]+cost[i-1], dp[i-2]+cost[i-2]) dp[0]=0, dp[1]=0
Unbounded knapsack min coins for amount i min(dp[i-coin] + 1) over all coins dp[0]=0
Unbounded count ways to make amount i dp[i] + dp[i-coin] over all coins dp[0]=1
0/1 Knapsack can we reach capacity i dp[i] || dp[i-num] (backward loop) dp[0]=true
Sequence (LIS-style) best result ending at index i max(dp[j]+1) for all j<i where valid dp[i]=1
2D Grid ways/value at cell (i,j) dp[i-1][j] + dp[i][j-1] row 0 and col 0 = 1
2D String (LCS) LCS length of first i and j chars dp[i-1][j-1]+1 if match, else max(dp[i-1][j], dp[i][j-1]) row 0 and col 0 = 0

5 Main Patterns

Pattern 1: Linear 1D DP

// dp[i] depends on 1 or 2 previous states
int[] dp = new int[n + 1];
dp[0] = BASE_0;
dp[1] = BASE_1;
for (int i = 2; i <= n; i++) {
    dp[i] = combine(dp[i-1], dp[i-2]);
}

Pattern 2: 2D Grid / String DP

// dp[i][j] depends on dp[i-1][j], dp[i][j-1], or dp[i-1][j-1]
int[][] dp = new int[m + 1][n + 1];
for (int i = 1; i <= m; i++) {
    for (int j = 1; j <= n; j++) {
        if (match(i, j)) {
            dp[i][j] = dp[i-1][j-1] + 1;
        }
        else {
            dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);
        }
    }
}

Pattern 3: 0/1 Knapsack (Subset DP)

// Each item used at most once — iterate BACKWARD to prevent reuse
boolean[] dp = new boolean[target + 1];
dp[0] = true;
for (int num : nums) {
    for (int i = target; i >= num; i--) {  // backward = no reuse
        dp[i] = dp[i] || dp[i - num];
    }
}

Pattern 4: Unbounded Knapsack

// Items can be reused — iterate FORWARD (inner loop)
int[] dp = new int[amount + 1];
Arrays.fill(dp, amount + 1);    // sentinel for "unreachable"
dp[0] = 0;
for (int i = 1; i <= amount; i++) {
    for (int coin : coins) {
        if (i >= coin) {
            dp[i] = Math.min(dp[i], dp[i - coin] + 1);
        }
    }
}

Pattern 5: Sequence DP (LIS-style)

// dp[i] = best result of sequence ending at index i
// Must look back at all j < i
int[] dp = new int[n];
Arrays.fill(dp, 1);             // every element is length-1 sequence
for (int i = 1; i < n; i++) {
    for (int j = 0; j < i; j++) {
        if (nums[j] < nums[i]) {
            dp[i] = Math.max(dp[i], dp[j] + 1);
        }
    }
}
int ans = 0;
for (int val : dp) {
    ans = Math.max(ans, val);
}

General Templates

Space-Optimized 1D DP

// When dp[i] only needs dp[i-1] and dp[i-2]
int prev2 = BASE_0, prev1 = BASE_1;
for (int i = 2; i <= n; i++) {
    int curr = combine(prev1, prev2);
    prev2 = prev1;
    prev1 = curr;
}
return prev1;

0/1 Knapsack (Minimize)

// dp[i] = min cost to achieve capacity i
int[] dp = new int[capacity + 1];
Arrays.fill(dp, Integer.MAX_VALUE);
dp[0] = 0;
for (int item : items) {
    for (int i = capacity; i >= item; i--) {   // backward
        if (dp[i - item] != Integer.MAX_VALUE) {
            dp[i] = Math.min(dp[i], dp[i - item] + cost);
        }
    }
}

Problem 1: Climbing Stairs ⭐ MUST DO [B75-70]

Difficulty Time to Solve Pattern
Easy 5-10 min Linear 1D DP

You can climb 1 or 2 steps at a time. Return the number of distinct ways to reach step n.

Example:

Input:  n = 2  →  2   (1+1 or 2)
Input:  n = 3  →  3   (1+1+1, 1+2, 2+1)
Input:  n = 5  →  8

Key Insight:

  • To reach stair i, you either came from stair i-1 (took 1 step) or stair i-2 (took 2 steps). So the total ways = dp[i-1] + dp[i-2]. This is the Fibonacci sequence.
  • Formula: dp[i] = dp[i-1] + dp[i-2], base cases dp[0]=1, dp[1]=1

Solution:

public int climbStairs(int n) {
    if (n <= 2) {
        return n;
    }
    int prev2 = 1, prev1 = 2;
    for (int i = 3; i <= n; i++) {
        int curr = prev1 + prev2;
        prev2 = prev1;
        prev1 = curr;
    }
    return prev1;
}

Complexity:

Time O(n) — single pass
Space O(1) — two variables

Variants — same pattern, different formula:

Allow 3 steps (1, 2, or 3):

// dp[i] = dp[i-1] + dp[i-2] + dp[i-3]
// base: dp[0]=1, dp[1]=1, dp[2]=2
// climbStairs(7) = 44
dp[i] = dp[i-1] + dp[i-2] + dp[i-3];

Minimum cost climbing stairs (LC #746): Each stair has a cost. You can start from index 0 or 1. Reach the top (index n) with minimum cost.

// dp[i] = min cost to reach position i
// dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2])
// base: dp[0]=0, dp[1]=0  (free to start at either)
// cost=[1,100,1,1,100,1] → answer: 4
public int minCostClimbingStairs(int[] cost) {
    int n = cost.length;
    int[] dp = new int[n + 1];
    for (int i = 2; i <= n; i++) {
        dp[i] = Math.min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2]);
    }
    return dp[n];
}

Problem 2: Jump Game ⭐ MUST DO [B75-55]

Difficulty Time to Solve Pattern
Medium 10-15 min Greedy / Linear DP

Given an array where nums[i] is your max jump length from index i, return true if you can reach the last index.

Example:

Input:  [2, 3, 1, 1, 4]  →  true   (0→1→4)
Input:  [3, 2, 1, 0, 4]  →  false  (always land on index 3 where nums[3]=0)

Key Insight:

  • Track the farthest index reachable at each step. If the current index i exceeds maxReach, you are stuck — you cannot reach index i or anything beyond it.

Solution:

public boolean canJump(int[] nums) {
    int maxReach = 0;
    for (int i = 0; i < nums.length; i++) {
        if (i > maxReach) {
            return false;         // can't reach index i
        }
        maxReach = Math.max(maxReach, i + nums[i]);
    }
    return true;
}

Complexity:

Time O(n) — single pass
Space O(1)

Problem 3: House Robber ⭐ MUST DO [B75-198]

Difficulty Time to Solve Pattern
Medium 10-15 min Linear 1D DP

Rob houses in a row without robbing two adjacent ones. Return the maximum amount you can rob.

Example:

Input:  [1, 2, 3, 1]    →  4   (rob houses 1 and 3)
Input:  [2, 7, 9, 3, 1] →  12  (rob houses 1, 3, 5)

Key Insight:

  • At each house, decide: skip it (take the previous max) or rob it (take prev-prev max + current value). The max of those two choices is the best you can do through house i.

Solution:

public int rob(int[] nums) {
    int prev2 = 0, prev1 = 0;
    for (int num : nums) {
        int curr = Math.max(prev1, prev2 + num);
        prev2 = prev1;
        prev1 = curr;
    }
    return prev1;
}

Complexity:

Time O(n) — single pass
Space O(1)

Problem 4: House Robber II ⭐ MUST DO [B75-213]

Difficulty Time to Solve Pattern
Medium 15-20 min Linear 1D DP

Same as House Robber but houses are arranged in a circle — the first and last are adjacent. Return the maximum amount.

Example:

Input:  [2, 3, 2]    →  3   (can't rob both house 1 and 3)
Input:  [1, 2, 3, 1] →  4

Key Insight:

  • You cannot rob both the first and last house. Run the linear House Robber twice: once on nums[0..n-2] (exclude last), once on nums[1..n-1] (exclude first). Take the max.

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;
}

Complexity:

Time O(n) — two linear passes
Space O(1)

Problem 5: Unique Paths ⭐ MUST DO [B75-62]

Difficulty Time to Solve Pattern
Medium 10-15 min 2D Grid DP

Count all unique paths from top-left to bottom-right of an m × n grid, moving only right or down.

Example:

Input:  m = 3, n = 7  →  28
Input:  m = 3, n = 2  →  3

Key Insight:

  • dp[i][j] = dp[i-1][j] + dp[i][j-1]: paths to any cell equal the sum of paths from the cell above and the cell to the left. The entire first row and first column are 1 (only one way to reach them).

Solution:

public int uniquePaths(int m, int n) {
    int[] dp = new int[n];
    Arrays.fill(dp, 1);                 // top row: 1 path to each cell
    for (int i = 1; i < m; i++) {
        for (int j = 1; j < n; j++) {
            dp[j] += dp[j - 1];         // above (dp[j]) + left (dp[j-1])
        }
    }
    return dp[n - 1];
}

Complexity:

Time O(m × n)
Space O(n) — 1D rolling array

Problem 6: Coin Change ⭐ MUST DO [B75-322]

Difficulty Time to Solve Pattern
Medium 15-20 min Unbounded Knapsack

Return the fewest coins needed to make up the amount. Coins can be used any number of times. Return -1 if impossible.

Example:

Input:  coins = [1, 5, 11],  amount = 15  →  3   (5+5+5)
Input:  coins = [2],          amount = 3   →  -1

Key Insight:

  • Unbounded knapsack: coins are reusable. Fill dp left to right. dp[i] = minimum coins to make amount i. Try every coin at each amount — if dp[i - coin] is reachable, dp[i] might be updated.

Solution:

public int coinChange(int[] coins, int amount) {
    int[] dp = new int[amount + 1];
    Arrays.fill(dp, amount + 1);    // sentinel for "unreachable"
    dp[0] = 0;
    for (int i = 1; i <= amount; i++) {
        for (int coin : coins) {
            if (i >= coin) {
                dp[i] = Math.min(dp[i], dp[i - coin] + 1);
            }
        }
    }
    return dp[amount] > amount ? -1 : dp[amount];
}

Complexity:

Time O(amount × coins)
Space O(amount)

Problem 7: Decode Ways ⭐ MUST DO [B75-91]

Difficulty Time to Solve Pattern
Medium 20-25 min Linear String DP

A string of digits can be decoded as letters (1→A through 26→Z). Return the number of ways to decode the string.

Example:

Input:  "12"   →  2   ("AB" or "L")
Input:  "226"  →  3   ("BZ", "VF", "BBF")
Input:  "06"   →  0   (leading zero — invalid)

Key Insight:

  • dp[i] = number of ways to decode s[0..i-1]. At each position, check two cases: the single digit s[i-1] (valid if not '0'), and the two-digit number s[i-2..i-1] (valid if in range 10–26). Add dp[i-1] or dp[i-2] accordingly.

Solution:

public int numDecodings(String s) {
    int n = s.length();
    int[] dp = new int[n + 1];
    dp[0] = 1;                                  // empty prefix: 1 way
    dp[1] = s.charAt(0) == '0' ? 0 : 1;

    for (int i = 2; i <= n; i++) {
        int oneDigit = Integer.parseInt(s.substring(i - 1, i));
        int twoDigit = Integer.parseInt(s.substring(i - 2, i));
        if (oneDigit >= 1) {
            dp[i] += dp[i - 1];                 // valid single digit
        }
        if (twoDigit >= 10 && twoDigit <= 26) {
            dp[i] += dp[i - 2];                 // valid two digit
        }
    }
    return dp[n];
}

Complexity:

Time O(n) — single pass
Space O(n) — dp array

Problem 8: Word Break ⭐ MUST DO [B75-139]

Difficulty Time to Solve Pattern
Medium 20-25 min String DP

Return true if string s can be segmented into a space-separated sequence of dictionary words.

Example:

Input:  s = "leetcode",   wordDict = ["leet","code"]                →  true
Input:  s = "catsandog",  wordDict = ["cats","dog","sand","and","cat"]  →  false

Key Insight:

  • dp[i] = true if s[0..i-1] can be fully segmented. For each end position i, scan all start positions j: if dp[j] is true and s[j..i-1] is in the dictionary, then dp[i] = true.

Solution:

public boolean wordBreak(String s, List<String> wordDict) {
    Set<String> dict = new HashSet<>(wordDict);
    boolean[] dp = new boolean[s.length() + 1];
    dp[0] = true;
    for (int i = 1; i <= s.length(); i++) {
        for (int j = 0; j < i; j++) {
            if (dp[j] && dict.contains(s.substring(j, i))) {
                dp[i] = true;
                break;
            }
        }
    }
    return dp[s.length()];
}

Complexity:

Time O(n² × m) where m is average word length (substring creation cost)
Space O(n + d) where d is dictionary size

Problem 9: Partition Equal Subset Sum ⭐ MUST DO

Difficulty Time to Solve Pattern
Medium 20-25 min 0/1 Knapsack

Determine if an array can be partitioned into two subsets with equal sum.

Example:

Input:  [1, 5, 11, 5]  →  true   ({1,5,5} and {11})
Input:  [1, 2, 3, 5]   →  false

Key Insight:

  • Equivalent to: can we find a subset that sums to total/2? If total is odd, immediately false.
  • Use 0/1 knapsack on a boolean dp array. Iterate backward so each element is considered at most once.

Solution:

public boolean canPartition(int[] nums) {
    int total = 0;
    for (int n : nums) {
        total += n;
    }
    if (total % 2 != 0) {
        return false;
    }

    int target = total / 2;
    boolean[] dp = new boolean[target + 1];
    dp[0] = true;

    for (int num : nums) {
        for (int i = target; i >= num; i--) {   // backward = no reuse
            dp[i] = dp[i] || dp[i - num];
        }
    }
    return dp[target];
}

Complexity:

Time O(n × target) where target = total/2
Space O(target)

Problem 10: Longest Increasing Subsequence ⭐ MUST DO [B75-300]

Difficulty Time to Solve Pattern
Medium 20-25 min Sequence DP

Return the length of the longest strictly increasing subsequence.

Example:

Input:  [10, 9, 2, 5, 3, 7, 101, 18]  →  4   ([2, 3, 7, 101])
Input:  [0, 1, 0, 3, 2, 3]            →  4   ([0, 1, 2, 3])

Key Insight:

  • dp[i] = length of the LIS ending exactly at index i. For each i, scan all j < i: if nums[j] < nums[i], dp[i] = max(dp[i], dp[j] + 1).
  • The answer is the max over all dp[i] — the LIS can end at any index, not just the last.

Solution:

public int lengthOfLIS(int[] nums) {
    int n = nums.length;
    int[] dp = new int[n];
    Arrays.fill(dp, 1);             // every element is a length-1 subsequence

    for (int i = 1; i < n; i++) {
        for (int j = 0; j < i; j++) {
            if (nums[j] < nums[i]) {
                dp[i] = Math.max(dp[i], dp[j] + 1);
            }
        }
    }

    int max = 0;
    for (int val : dp) {
        max = Math.max(max, val);
    }
    return max;
}

Complexity:

Time O(n²) — for each i, scan all j < i
Space O(n) — dp array

Problem 11: Combination Sum IV ⭐ MUST DO [B75-377]

Difficulty Time to Solve Pattern
Medium 15-20 min Unbounded Count DP

Return the number of ordered sequences (order matters) that add up to target. Numbers can be reused.

Example:

Input:  nums = [1, 2, 3],  target = 4  →  7
   (1+1+1+1, 1+1+2, 1+2+1, 1+3, 2+1+1, 2+2, 3+1)

Key Insight:

  • Order matters, so this counts permutations, not combinations. The key is to loop over target values in the outer loop and items in the inner loop — this lets you count all orderings.
  • dp[i] = number of ordered sequences summing to i.

Solution:

public int combinationSum4(int[] nums, int target) {
    int[] dp = new int[target + 1];
    dp[0] = 1;                          // one way to make 0: empty sequence
    for (int i = 1; i <= target; i++) {
        for (int num : nums) {
            if (i >= num) {
                dp[i] += dp[i - num];
            }
        }
    }
    return dp[target];
}

Complexity:

Time O(target × nums)
Space O(target)

Problem 12: Longest Common Subsequence ⭐ MUST DO [B75-1143]

Difficulty Time to Solve Pattern
Medium 20-25 min 2D String DP

Return the length of the longest common subsequence of two strings.

Example:

Input:  text1 = "abcde",  text2 = "ace"  →  3   ("ace")
Input:  text1 = "abc",    text2 = "abc"  →  3
Input:  text1 = "abc",    text2 = "def"  →  0

Key Insight:

  • dp[i][j] = LCS length of text1[0..i-1] and text2[0..j-1]. When characters match, extend the LCS from the diagonal. When they don't, take the best of skipping one character from either string.

Solution:

public int longestCommonSubsequence(String text1, String text2) {
    int m = text1.length(), n = text2.length();
    int[][] dp = new int[m + 1][n + 1];
    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= n; j++) {
            if (text1.charAt(i - 1) == text2.charAt(j - 1)) {
                dp[i][j] = dp[i-1][j-1] + 1;
            }
            else {
                dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);
            }
        }
    }
    return dp[m][n];
}

Complexity:

Time O(m × n)
Space O(m × n) — 2D table

Quick Reference Table

# Problem Pattern Key Technique Time
1 Climbing Stairs ⭐ [B75-70] Linear 1D DP dp[i] = dp[i-1] + dp[i-2] O(n)
2 Jump Game ⭐ [B75-55] Greedy/DP Track maxReach; return false if i > maxReach O(n)
3 House Robber ⭐ [B75-198] Linear 1D DP max(skip, rob + prev-prev) O(n)
4 House Robber II ⭐ [B75-213] Linear 1D DP Two-pass: skip first or skip last O(n)
5 Unique Paths ⭐ [B75-62] 2D Grid DP dp[i][j] = above + left, 1D rolling array O(m×n)
6 Coin Change ⭐ [B75-322] Unbounded Knapsack Forward fill, sentinel = amount+1 O(A×C)
7 Decode Ways ⭐ [B75-91] Linear String DP 1-digit and 2-digit decode checks O(n)
8 Word Break ⭐ [B75-139] String DP Reachability via substring dict lookup O(n²)
9 Partition Equal Subset Sum ⭐ 0/1 Knapsack Backward fill, boolean dp O(n×S)
10 LIS ⭐ [B75-300] Sequence DP dp[i] = max(dp[j]+1) for j<i, nums[j]<nums[i] O(n²)
11 Combination Sum IV ⭐ [B75-377] Unbounded Count Outer=target, inner=nums (permutations) O(T×N)
12 LCS ⭐ [B75-1143] 2D String DP Match: +1 from diagonal; no match: max skip O(m×n)

When to Use Each Pattern

Linear 1D DP

  • Problem involves a sequence and the current state depends on 1–2 previous states
  • Classic signals: "can't pick adjacent", "ways to reach step n", "count valid decodings"
  • Space-optimize by keeping only prev1 and prev2 when the full array isn't needed

2D Grid / String DP

  • Grid path problems where you move right or down (Unique Paths, Min Path Sum)
  • Two-string matching problems (LCS, edit distance, longest palindromic subsequence)
  • dp dimensions = lengths of the two inputs; extra row/col for the empty-string base case

0/1 Knapsack (Subset DP)

  • Selecting items with a budget where each item is used at most once
  • Iterate the capacity dimension backward — this prevents reusing the same item
  • Boolean variant: "can we achieve exactly X sum?"; integer variant: min/max value at capacity X

Unbounded Knapsack

  • Items are reusable (coins, tiles, numbers from an array)
  • Iterate the capacity dimension forward — reuse is allowed
  • Outer loop on capacity/target, inner loop on items (for min/max problems)
  • For count-of-permutations: outer loop on target, inner loop on items

Sequence DP (LIS-style)

  • Finding the optimal subsequence in an array — length, count, or value
  • dp[i] = best result of a sequence ending at index i, not best up to i
  • Answer is max over all dp[i], not dp[n-1], since the optimal subsequence can end anywhere

Common Mistakes to Avoid

General DP Mistakes

  1. Wrong state definition — dp[i] must have a clear, precise meaning; if you can't state it in plain English, the recurrence will be wrong
  2. Wrong answer location — the answer is not always dp[n]; for LIS it is the max over all dp[i]; for grid DP it is dp[m-1][n-1]
  3. Off-by-one in array sizing — for string/array inputs, use dp[n+1] with dp[0] as the empty-prefix base case

1D DP Mistakes

  1. Wrong base cases — Climbing Stairs needs dp[1]=1 and dp[2]=2; Decode Ways needs dp[0]=1 (empty string); always verify the first two values manually
  2. Decode Ways: not handling '0' — a single '0' cannot be decoded; forgetting this gives wrong answers for inputs like "10" or "30"

Knapsack Mistakes

  1. Forward instead of backward in 0/1 knapsack — iterating forward allows using the same item multiple times; always iterate backward for 0/1 (each item once)
  2. Forgetting the unreachable sentinel — in Coin Change, initialize dp with amount + 1 (not 0); initializing with 0 makes every amount appear reachable

2D DP Mistakes

  1. Skipping base row/column initialization — for grid DP, the first row and column must be set before the main fill loop; they have only one path each

Sequence DP Mistakes

  1. Returning dp[n-1] for LIS — the LIS can end at any index; return max over all dp[i], not dp[n-1]
  2. Wrong loop order for Combination Sum IV — order matters (permutations); the outer loop must be over target values, not items; items-outer would count combinations instead