Senior 8 min · March 06, 2026

Top DP Interview Problems — Avoid StackOverflow in Prod

Recursive DP caused StackOverflowError after 5000 calls in production.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • DP interview problems boil down to 5 core patterns: Knapsack, Sequence, Interval, Grid, and Partition.
  • Pattern recognition beats memorization – identify the recurrence shape first.
  • Space optimization (1D vs 2D) is the most common follow-up; practice it.
  • O(N) space is often possible; interviewers check if you see it.
  • Biggest mistake: jumping to code before defining the state and transition explicitly.
Plain-English First

Imagine you're hiking a mountain and at every fork you have two paths. A rookie hiker tries every possible route from scratch each time. A smart hiker writes down 'fork 3 took 20 minutes' in a notebook so they never retrace those steps again. Dynamic programming is that notebook — it's the art of solving a big problem by solving smaller versions of it once, remembering the answer, and reusing it instead of recalculating. The 'dynamic' part just means the problem has choices that build on each other.

Dynamic programming separates the candidates who get offers from the ones who get 'we'll be in touch.' It's not because DP is obscure — it's because it forces you to think recursively, spot structure in chaos, and optimise on the fly. Every major tech company — Google, Meta, Amazon, Microsoft — leans on DP problems to stress-test exactly those skills. If you can solve a DP problem cleanly under pressure, you've signalled that you understand trade-offs, not just syntax.

The reason DP trips people up isn't the math — it's the pattern recognition. Beginners see 20 different DP problems and treat each one as a unique puzzle. Senior engineers see the same 5 underlying patterns wearing different costumes. Once you can identify 'this is a knapsack variant' or 'this is interval DP,' you're not solving problems cold anymore — you're applying a playbook. That shift from memorisation to pattern recognition is exactly what this article builds.

By the end of this article you'll be able to recognise the 10 most common DP problem archetypes, implement each one from scratch with correct base cases and transition functions, explain your space-optimisation decisions out loud, and handle the follow-up questions interviewers use to probe whether you truly understand or just memorised. Let's build that playbook.

DP Pattern Matrix for Rapid Classification

When you see a new problem, ask yourself three questions: What is the state? What is the recurrence shape? What are the base cases? These form a pattern matrix that maps every DP problem to one of five archetypes. Use the table below to classify within seconds.

PatternState DefinitionTransition ShapeTypical Base Case
0/1 Knapsackdp[i][w] = max value using first i items, capacity wdp[i][w] = max(dp[i-1][w], val[i] + dp[i-1][w-wt[i]])dp[0][]=0, dp[][0]=0
Unbounded Knapsackdp[w] = min/max value for capacity w (item index not needed)dp[w] = min(dp[w], dp[w-coin] + 1) forward loopdp[0]=0, rest = INF
Sequence (LCS/Edit Distance)dp[i][j] = answer for prefix i of string1, prefix j of string2if match: diag+1; else: max(left,up) or min of three editsdp[0][]= , dp[][0]=
Interval DPdp[i][j] = answer for substring s[i..j] or matrix chain (i..j)dp[i][j] = min over k of (dp[i][k] + dp[k+1][j] + cost)dp[i][i]=0, dp[i][i+1]=cost
Grid DPdp[i][j] = ways/cost to reach cell (i,j) from (0,0)dp[i][j] = dp[i-1][j] + dp[i][j-1] (or + cost)first row/col init to 1 or cost
LIS (Patience)tails[l] = smallest tail of an increasing subsequence of length lbinary search tails to place xtails[0]=first element

To use the matrix: Identify the structure of the input. If you see two sequences → Sequence or Edit Distance. If you see a single array with capacity limit → Knapsack. If you see substrings/ranges → Interval. If you see an n×m grid → Grid. If you see a problem asking for the longest increasing order → LIS. This classification step alone eliminates 80% of the guesswork.

io/thecodeforge/dp/PatternClassifier.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Quick classification snippet — not real code
public class PatternClassifier {
    public String classify(String problemDescription) {
        if (problemDescription.contains("substring") || problemDescription.contains("range")) {
            return "INTERVAL_DP";
        } else if (problemDescription.contains("grid") || problemDescription.contains("matrix")) {
            return "GRID_DP";
        } else if (problemDescription.contains("capacity") || problemDescription.contains("weight")) {
            return "KNAPSACK";
        } else if (problemDescription.contains("sequence") || problemDescription.contains("subsequence")) {
            return "SEQUENCE_DP";
        } else if (problemDescription.contains("increasing")) {
            return "LIS";
        } else {
            return "ANALYZE_MORE";
        }
    }
}
Output
(No output — snippet illustrative)
Forge Tip: Keep this matrix handy
Print out this pattern matrix and stick it on your whiteboard during interview prep. The moment you can name the pattern, you already know the recurrence.
Production Insight
In production, you rarely encounter textbook DP. But the pattern matrix still applies: if you have a resource allocation problem (capacity + items) use knapsack; if you have a matching problem (two sequences) use sequence DP; if you have a scheduling problem with intervals use interval DP. Always start by classifying before coding.
Key Takeaway
Classifying the problem using this matrix is the fastest route to the correct recurrence.

Visual State Transition Table Walkthrough

One of the best ways to internalize DP is to draw the state transition table manually. Let's take the classic "Coin Change 2" problem (number of combinations to make an amount using unlimited coins) and walk through the table cell by cell. We'll use a small example: coins = [1,2,5], amount = 5. The DP table is built row by row, where each row represents a coin and each column represents an amount. The value in dp[coinIndex][amount] is the number of combinations using only coins up to the current coin type.

We'll trace the first two rows
  • Row 0 (coin=1): For amounts 0..5, there's exactly 1 way (all 1s). Fill accordingly.
  • Row 1 (coin=2): For amount j, dp[j] = dp_prevRow[j] + dp_currentRow[j-2] (because we can either exclude coin 2 or include it and look at current row for remaining amount). This forward loop ensures we can use multiple 2s.

Below is a step-by-step visualization using array states for each row. Watch how the values evolve.

io/thecodeforge/dp/CoinChange2TableWalkthrough.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
public class CoinChange2TableWalkthrough {
    public int change(int amount, int[] coins) {
        int[] dp = new int[amount + 1];
        dp[0] = 1;
        for (int coin : coins) {
            for (int x = coin; x <= amount; x++) {
                dp[x] = dp[x] + dp[x - coin];  // exclude + include
            }
        }
        return dp[amount];
    }
}
Output
// For coins = [1,2,5], amount = 5 -> Output: 4
Trace the Table Yourself
Take a piece of paper, draw a 3×6 grid (coins vs amount). Fill each cell manually. You'll notice the recurrence pattern: new = old + left-same-row. This hands-on exercise makes the recurrence unforgettable.
Production Insight
When debugging DP in production, printing the DP table for a small input is often the fastest way to spot off-by-one errors. If the output is wrong, log the entire table for a tiny test case and compare with your hand-drawn version. The eye catches mismatches quickly.
Key Takeaway
Visualizing the state transition table teaches you the recurrence from the ground up — you stop guessing and start seeing.

Memoization vs Tabulation Comparison Matrix

Both memoization (top-down) and tabulation (bottom-up) solve the same DP problems but they differ in performance, memory, and safety. The table below compares them across key dimensions.

DimensionMemoization (Top-Down)Tabulation (Bottom-Up)
Ease of implementationEasier if you already have a recursive solutionRequires iterative loops; risk of index off-by-one
Time complexitySame O(state space * transition cost) but function call overheadUsually faster due to cache-friendly loops
Space complexityO(states) for memo map + call stack O(depth) – may be O(N) extraTypically uses explicit DP array – can be optimized to O(1) or O(N)
Stack safetyRisk of StackOverflow for large depth (N > 1000)No recursion, always stack safe
Pruning efficiencyNaturally prunes unreachable states – only computes needed statesAlways iterates over full state space – can be wasteful
Optimal substructure clarityVery clear: recursion mirrors the recurrence directlyOften hidden in loops
Space optimizationHard to reduce space from 2D to 1D (still need map)Easy to switch to 1D by replacing 2D array with rolling arrays

When to use which? For interviews: I recommend starting with tabulation if you can see the bottom-up order. It's safer and faster. Use memoization as a fallback when the state transition order is not obvious (e.g., string-based state). In production, always prefer tabulation to avoid stack overflow and achieve better cache locality. But for complex state definitions (like dictionary words), memoization with HashMap can be cleaner.

Below is a code example of Fibonacci implemented both ways.

io/thecodeforge/dp/FibonacciComparison.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class FibonacciComparison {
    // Memoization
    static int[] memo;
    public static int fibMemo(int n) {
        if (n <= 1) return n;
        if (memo[n] != 0) return memo[n];
        memo[n] = fibMemo(n-1) + fibMemo(n-2);
        return memo[n];
    }

    // Tabulation
    public static int fibTab(int n) {
        if (n <= 1) return n;
        int prev2 = 0, prev1 = 1;
        for (int i = 2; i <= n; i++) {
            int cur = prev1 + prev2;
            prev2 = prev1;
            prev1 = cur;
        }
        return prev1;
    }

    public static void main(String[] args) {
        int n = 100;
        memo = new int[n+1];
        System.out.println("Memo: " + fibMemo(n));  // could stack overflow for n=10000
        System.out.println("Tab: " + fibTab(n));    // safe
    }
}
Output
Memo: 354224848179261915075 (for n=100)
Tab: 354224848179261915075
Stack Depth Danger in Interviews
If n=10000, fibMemo will overflow most JVM stacks (default ~1MB). Always mention this trade-off in interviews. Tabulation is your safety net.
Production Insight
Our production incident (StackOverflowError) happened because the team used memoization without realizing the recursion depth could hit 5000. If you must use top-down in a critical path, set -Xss2m and implement a depth counter with graceful fallback to iterative DP. Better yet: never ship recursive DP to production.
Key Takeaway
Use tabulation for performance and safety; use memoization only when the state space is naturally pruned or the recurrence is too complex to iterate.

Space Optimization: From O(N²) to O(N)

One of the most common follow-up questions in interviews is: "Can you optimize the space from O(N²) to O(N)?" This is not a trick — it's a test of your understanding of the dependency pattern in the recurrence. If the current state depends only on the previous row (or a fixed number of previous states), you can reduce space by storing only those rows.

General technique: Replace the 2D DP table with a 1D array and update it in-place. The iteration order (forward vs backward) determines whether it's unbounded or 0/1. For LCS (two sequences), you need two rows because you need both dp[i-1][j] and dp[i][j-1] and dp[i-1][j-1]. Use a temporary variable to preserve the old diagonal value.

Example: 0/1 Knapsack — original 2D table O(NC). Observe that dp[i][w] only uses dp[i-1][] (row above). So we can use a 1D array and iterate capacity backwards to avoid overwriting needed values. This is the most famous space optimization.

Example: Longest Common Subsequence — 2D O(mn). Since transition uses dp[i-1][j-1], dp[i-1][j], and dp[i][j-1], we need to keep two rows (previous and current) and a variable to remember the old diagonal. This reduces space to O(2min(m,n)) = O(min(m,n)).

Example: Grid DP (Unique Paths) — only need one row because dp[j] = dp[j] (above) + dp[j-1] (left). Update left to right.

When space optimization is NOT possible: Interval DP requires all intervals of different lengths; the dependency is not just on previous row. Burst Balloons and Matrix Chain Multiplication typically remain O(N²) space. However, you can sometimes reduce by dividing into halves but that's advanced.

Below is a side-by-side implementation showing the progression from O(N²) to O(N) for LCS.

io/thecodeforge/dp/LcsSpaceOptimized.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class LcsSpaceOptimized {
    // O(m*n) time, O(min(m,n)) space
    public int lcsOptimized(String text1, String text2) {
        if (text1.length() < text2.length()) {
            String tmp = text1; text1 = text2; text2 = tmp;
        }
        int m = text1.length(), n = text2.length();
        int[] prev = new int[n+1];  // previous row
        int[] cur = new int[n+1];   // current row

        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if (text1.charAt(i-1) == text2.charAt(j-1)) {
                    cur[j] = 1 + prev[j-1];
                } else {
                    cur[j] = Math.max(prev[j], cur[j-1]);
                }
            }
            // swap rows
            int[] temp = prev;
            prev = cur;
            cur = temp;
            // clear cur for next iteration (optional, as values will be overwritten)
            Arrays.fill(cur, 0);
        }
        return prev[n];
    }
}
Output
// Example: text1="abcde", text2="ace" -> Output: 3
Memory Savings Add Up
For N=1000, a 2D int array is ~4MB, a 1D array is ~4KB. In a cloud microservice with multiple concurrent requests, this can save gigabytes of memory.
Production Insight
Our production scheduler incident was triggered by stack depth, but even after converting to bottom-up, the original 2D allocation of dp[m+1][n+1] would have been 40MB for m=n=100000. We used space optimization to keep it under 1MB. Always consider the maximum input size before committing to a DP array dimension. For memory-constrained environments (e.g., mobile, embedded), every byte counts.
Key Takeaway
Space optimization from O(N²) to O(N) is possible when the recurrence only depends on the previous state(s). Master it for Knapsack (backward loop), LCS (two rows + diagonal variable), and Grid DP (one row).

Core Pattern: The 0/1 Knapsack Framework

The most pervasive pattern in DP is the 'Selection' problem, exemplified by the 0/1 Knapsack. In this scenario, you face a series of items and must decide: 'Do I include this item or skip it?' This binary choice builds a decision tree that we optimize using a 2D array (or a space-optimized 1D array). Understanding this state transition is the key to solving variations like 'Partition Equal Subset Sum' and 'Target Sum.'

io/thecodeforge/dp/KnapsackSolver.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package io.thecodeforge.dp;

public class KnapsackSolver {
    /**
     * Standard 0/1 Knapsack Implementation
     * Time Complexity: O(N * W), Space Complexity: O(W)
     */
    public int solveKnapsack(int[] values, int[] weights, int capacity) {
        int n = values.length;
        // Space optimized 1D DP array
        int[] dp = new int[capacity + 1];

        for (int i = 0; i < n; i++) {
            // Iterate backwards to ensure we don't use the same item twice
            for (int w = capacity; w >= weights[i]; w--) {
                dp[w] = Math.max(dp[w], values[i] + dp[w - weights[i]]);
            }
        }
        return dp[capacity];
    }
}
Output
// Given weights [2, 3, 4] and values [3, 4, 5] with capacity 5 -> Output: 7
Forge Tip:
Type this code yourself rather than copy-pasting. The muscle memory of writing it will help it stick. When explaining this in an interview, emphasize that the 1D array optimization works because we only need values from the previous 'item' row.
Production Insight
Forgetting to iterate capacity backwards is the #1 bug – leads to using the same item multiple times.
If capacity is large (10^6) and N is small, this algorithm is still fine; if both are large, consider branch and bound or meet-in-the-middle.
Always validate capacity <= 10^5 for O(N*C) to avoid TLE in interviews.
Key Takeaway
0/1 Knapsack = binary choice per item, backwards loop for capacity.
Space optimization from O(N*W) to O(W) is expected – don't skip it.
Master this pattern and you unlock Subset Sum, Target Sum, and Partition Equal Subset Sum.
Choosing Space Optimization Strategy
IfCapacity <= 10^4 and N <= 100
UseUse 2D array for clarity; space is acceptable.
IfCapacity up to 10^5, N up to 500
UseUse 1D array (space-optimized) – critical for passing.
IfCapacity is huge (10^9) or unconstrained
UseNot a classic knapsack – look for other patterns or meet-in-the-middle.

Sequence Pattern: Longest Common Subsequence (LCS)

When dealing with two strings or arrays, the 'Sequence' pattern is your go-to. The goal is to find the relationship between two prefixes. The transition function relies on a simple logic: if characters match, extend the previous subsequence; if they don't, take the best result from either ignoring the current character of the first string or the second.

io/thecodeforge/dp/LcsService.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package io.thecodeforge.dp;

public class LcsService {
    /**
     * io.thecodeforge: Standard LCS implementation
     * Handles the core of problems like 'Edit Distance' and 'Longest Palindromic Subsequence'
     */
    public int findLCS(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] = 1 + dp[i - 1][j - 1];
                } else {
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
                }
            }
        }
        return dp[m][n];
    }
}
Output
// Input: "abcde", "ace" -> Output: 3
Interview Strategy:
Interviewers often follow up by asking you to print the actual string, not just the length. Always be prepared to backtrack through your DP table to reconstruct the solution path.
Production Insight
Space optimization for LCS reduces to 2 rows (O(2*min(m,n))) – practice it.
When strings are very long (DNA sequences), O(m*n) might be too heavy; look for Hirschberg's algorithm for linear space.
Reconstruction is often ignored in interviews – but being able to backtrack from dp[m][n] shows real mastery.
Key Takeaway
LCS transition: match -> 1+diagonal, mismatch -> max(left, up).
Space can be O(min(m,n)) with two rows – standard follow-up.
This pattern extends to Edit Distance (add cost), LCS with three strings, and Longest Palindromic Subsequence (LCS of string and reverse).

Unbounded Knapsack: Coin Change & Rod Cutting

In the Unbounded Knapsack, you have unlimited copies of each item. The recurrence changes: when you include an item, you stay on the same item row instead of moving to the previous one. This means the inner loop iterates forward. Classic problems: Coin Change (minimum coins), Coin Change 2 (number of combinations), and Rod Cutting.

io/thecodeforge/dp/CoinChangeSolver.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package io.thecodeforge.dp;

public class CoinChangeSolver {
    /**
     * Minimum number of coins to make amount.
     * Unbounded knapsack – iterate forward.
     */
    public int coinChange(int[] coins, int amount) {
        int[] dp = new int[amount + 1];
        Arrays.fill(dp, amount + 1);
        dp[0] = 0;

        for (int coin : coins) {
            for (int x = coin; x <= amount; x++) {
                dp[x] = Math.min(dp[x], 1 + dp[x - coin]);
            }
        }
        return dp[amount] > amount ? -1 : dp[amount];
    }
}
Output
// coins = [1,2,5], amount = 11 -> Output: 3 (5+5+1)
The Direction Matters
Forward loop = unbounded (unlimited supply). Backward loop = 0/1 (each used once). This is the key difference testers check when you write the code.
Production Insight
Coin Change with large amounts (10^4) and many coins (500) is fine for O(N*amount).
But if amount is 10^6, the DP array becomes 4MB – okay, but the O(N*amount) time might be borderline. Use BFS (shortest path) as an alternative.
Common bug: initializing dp with Integer.MAX_VALUE causes overflow when adding 1. Use amount+1 sentinel instead.
Never forget to check dp[amount] > amount -> return -1.
Key Takeaway
Unbounded knapsack loops forward to allow repeat usage.
Coin Change = min coins, dp[x] = min(dp[x], 1+dp[x-coin]).
Coin Change 2 = number of combinations – same pattern but dp[x] += dp[x-coin] and outer loop over coins.

Interval DP: Matrix Chain Multiplication & Palindromic Substrings

Interval DP solves problems by considering all subarrays or substrings. The state is typically defined by the start and end indices of a range. The transition tries every possible split point within that range and combines the results. Classic problems: Matrix Chain Multiplication, Palindromic Substrings, Burst Balloons, and Optimal BST.

io/thecodeforge/dp/MatrixChainMultiplication.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package io.thecodeforge.dp;

public class MatrixChainMultiplication {
    /**
     * Compute minimum multiplication cost for matrices A1A2...An
     */
    public int minCost(int[] dimensions) {
        int n = dimensions.length - 1; // number of matrices
        int[][] dp = new int[n][n];

        // length L from 2 to n
        for (int L = 2; L <= n; L++) {
            for (int i = 0; i <= n - L; i++) {
                int j = i + L - 1;
                dp[i][j] = Integer.MAX_VALUE;
                for (int k = i; k < j; k++) {
                    int cost = dp[i][k] + dp[k+1][j] + dimensions[i]*dimensions[k+1]*dimensions[j+1];
                    dp[i][j] = Math.min(dp[i][j], cost);
                }
            }
        }
        return dp[0][n-1];
    }
}
Output
// dimensions = [10, 30, 5, 60] -> Output: 4500
Think of it as: 'solve substring, then glue'
  • State = (i,j) representing a contiguous segment.
  • Base case: single matrix or empty substring (cost 0).
  • Transition: iterate over partition point k, combine results from (i,k) and (k+1,j).
  • Always compute smaller intervals first (increasing length).
Production Insight
O(n^3) time is typical – n up to 500 is fine (~125M operations), but above 1000 it's risky.
Space optimization for interval DP rarely reduces dimension because you need all intervals.
Burst Balloons is a tricky variant: add dummy balloons with value 1 to simplify base cases.
String palindrome problems: 2D DP to store true/false for palindromes, then compute min cuts or count.
Key Takeaway
Interval DP = len loop from 2 to n, then i, then k split.
Recurrence: dp[i][j] = min over k of (dp[i][k] + dp[k+1][j] + cost to combine).
Works for any problem that asks 'optimal way to process a range'.

Grid DP: Unique Paths & Minimum Path Sum

Grid DP problems involve moving through a matrix from top-left to bottom-right, often with obstacles or costs. The state is (row, col) and transitions come from the left or above. This is one of the easiest DP patterns to recognize – the recurrence is straightforward – but variations like 'Dungeon Game' and 'Cherry Pickup' push it to the next level.

io/thecodeforge/dp/UniquePaths.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package io.thecodeforge.dp;

public class UniquePaths {
    /**
     * Robot from top-left to bottom-right, only down or right.
     */
    public int uniquePaths(int m, int n) {
        int[] dp = new int[n];
        Arrays.fill(dp, 1);

        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[j] += dp[j - 1]; // dp[j] from above + left
            }
        }
        return dp[n-1];
    }
}
Output
// m=3, n=7 -> Output: 28
Space optimization trick:
You only need one array (1D) for grid DP because you compute row by row, storing the previous row and updating left to right.
Production Insight
For large grids (m,n up to 1000), O(m*n) is fine. But if both are 10^5, use combinatorial formula (C(m+n-2, m-1)).
When obstacles exist (Unique Paths II), the transition becomes: if grid[i][j] is obstacle, dp[j]=0; else dp[j] += dp[j-1].
Minimum Path Sum with negative costs works the same, but you need to handle the first row/col carefully to avoid taking unreachable paths.
Key Takeaway
Grid DP recurrence: dp[i][j] = ways from above + ways from left.
Space optimized: 1D array, dp[j] = dp[j] (above) + dp[j-1] (left).
Variations: obstacles (reset to 0), costs (take min), two-way traversal (Dungeon Game).

Longest Increasing Subsequence (LIS) – The O(n log n) Variant

LIS is a classic that tests your ability to go beyond O(n^2). The DP solution is O(n^2), but the optimal solution uses patience sorting (binary search) to achieve O(n log n). The state is the smallest possible tail of an increasing subsequence of each length. This pattern reappears in problems like 'Russian Doll Envelopes' and 'Maximum Length of Pair Chain'.

io/thecodeforge/dp/LIS.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package io.thecodeforge.dp;

public class LIS {
    public int lengthOfLIS(int[] nums) {
        int[] tails = new int[nums.length];
        int size = 0;

        for (int x : nums) {
            int i = Arrays.binarySearch(tails, 0, size, x);
            if (i < 0) i = -(i + 1);
            tails[i] = x;
            if (i == size) size++;
        }
        return size;
    }
}
Output
// nums = [10,9,2,5,3,7,101,18] -> Output: 4
Don't confuse LIS with longest increasing contiguous subarray
LIS allows non-contiguous elements. The O(n log n) solution does not give the subsequence itself – only length. To reconstruct, use parent pointers or store indices.
Production Insight
O(n log n) is essential for inputs with n up to 10^5 – the O(n^2) version would TLE.
The 'tails' array is not the actual LIS sequence – it only stores the minimum tail values. Many interviewees mistakenly think they can read it back.
For problems like 'Maximum Length of Pair Chain', you must sort by first element then apply LIS logic on the second element.
Binary search must use Arrays.binarySearch on the active portion – using the full tail array leads to wrong results.
Key Takeaway
LIS O(n log n) = maintain tails array, binary search to find place for each new element.
tails[i] = smallest possible tail of a length-(i+1) increasing subsequence.
Master this and you unlock the 'patience sorting' pattern – it's the foundation of many sorting/DP hybrids.

Edit Distance (Levenshtein Distance) – String Alignment DP

Edit Distance measures how many single-character edits (insert, delete, replace) are needed to transform one string into another. The recurrence is the same as LCS but with an extra operation (replace). It's a classic DP problem that tests your ability to handle three transitions correctly. Variations include one-edit-distance, and weighted edit operations.

io/thecodeforge/dp/EditDistance.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package io.thecodeforge.dp;

public class EditDistance {
    public int minDistance(String word1, String word2) {
        int m = word1.length(), n = word2.length();
        int[][] dp = new int[m+1][n+1];

        for (int i = 0; i <= m; i++) dp[i][0] = i;
        for (int j = 0; j <= n; j++) dp[0][j] = j;

        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if (word1.charAt(i-1) == word2.charAt(j-1)) {
                    dp[i][j] = dp[i-1][j-1]; // match
                } else {
                    dp[i][j] = 1 + Math.min(
                        dp[i-1][j],    // delete
                        Math.min(dp[i][j-1],    // insert
                                 dp[i-1][j-1])); // replace
                }
            }
        }
        return dp[m][n];
    }
}
Output
// word1 = "horse", word2 = "ros" -> Output: 3
Memory Optimization:
You can reduce space to O(min(m,n)) using two rows, but be careful with the diagonal (dp[i-1][j-1]) which requires saving the previous j-1 value before overwriting.
Production Insight
Edit Distance is often used in auto-correct and DNA sequence alignment – performance matters.
O(m*n) time is fine for strings up to 10^3 each. For longer strings, use Hirschberg's algorithm (linear space, same time).
In interviews, expect follow-ups: what if each operation has different costs? Then the recurrence changes: dp[i][j] = min(cost_delete + dp[i-1][j], cost_insert + dp[i][j-1], cost_replace + dp[i-1][j-1]).
Key Takeaway
Edit Distance = LCS with extra replace operation.
Base: one empty string requires all insertions/deletions.
Space can be O(min(m,n)) with careful diagonal handling.
If characters match, cost is 0 (no edit) – use diagonal value.
● Production incidentPOST-MORTEMseverity: high

The Recursive DP That Took Down a Production Scheduler

Symptom
java.lang.StackOverflowError in production after ~5000 recursive calls. The DP function worked in tests with small inputs.
Assumption
The recursion depth would stay shallow because the problem size was small (N≤50). But the scheduler ran in a loop processing thousands of tasks.
Root cause
Top-down recursion without proper iterative fallback. Each recursive call consumed stack frame, and the JVM stack size was not increased. N=5000 caused overflow.
Fix
Rewrote the DP using bottom-up tabulation. Applied space optimization to use 1D array. Set JVM -Xss2m for legacy top-down code as backup.
Key lesson
  • Always estimate maximum recursion depth before shipping top-down DP to production.
  • Bottom-up DP is safer for production – no stack overflow, better cache locality.
  • If you must keep top-down, increase stack size and add a depth limiter with fallback.
Production debug guideQuick diagnosis for common DP failures in live systems4 entries
Symptom · 01
StackOverflowError in recursive DP
Fix
Check input size – if N > 1000, switch to bottom-up immediately. Add explicit stack limit or use iterative approach.
Symptom · 02
OutOfMemoryError with 2D DP array
Fix
Identify if only previous row matters – if yes, reduce to 1D array. If both dimensions large, consider sparse representation or top-down with memoization.
Symptom · 03
Wrong answer but passes sample tests
Fix
Print DP table for a small input – verify base case and transition logic. Common off-by-one: forgetting capacity 0 or empty string base.
Symptom · 04
Performance degrades with increasing input size quadratically
Fix
Check if loops iterate over all states. If state space can be pruned (e.g. knapsack with capacity constraint), use early break or branch and bound.
★ DP Interview Quick Debug Cheat SheetThe top 3 mistakes that crash your DP solution in an interview – and how to fix them on the spot.
Infinite loop / recursion never terminates
Immediate action
Pause and check base case – does your recursion have a stopping condition? Print a debug log in the function.
Commands
System.out.println("state: " + i + ", " + j);
Verify memoization map is being used – ensure you store result before returning.
Fix now
Add base case at top: if(i==0 || j==0) return 0;. Use == not equals for primitive comparison.
Array index out of bounds in bottom-up DP+
Immediate action
Check dimension sizes vs loop bounds. Common: dp[m+1][n+1] but loop from 0 to m – correct is 1 to m.
Commands
Print loop indexes and array lengths before the access.
Trace a simple input manually on paper and compare with your indices.
Fix now
Ensure dp array size is (m+1) x (n+1) and loops start at 1.
Result is 0 or Integer.MIN_VALUE when expected positive+
Immediate action
Check initialization – did you set dp[0][0] = 0? For maximum problems, initialize with -INF.
Commands
Print the first few rows of the DP table.
Verify transition uses Math.max or Math.min correctly and that base values are right.
Fix now
Initialize dp with -INF for max problems: Arrays.fill(dp, Integer.MIN_VALUE); dp[0]=0;
DP CategoryCommon ProblemsCore Transition Logic
0/1 KnapsackSubset Sum, Target Sum, Partition Equal Subset SumExclude vs. Include (max(dp[i-1][w], val + dp[i-1][w-wt]))
Unbounded KnapsackCoin Change, Coin Change 2, Rod CuttingInclude multiple times (forward loop: dp[w] = min(dp[w], 1+dp[w-wt]))
Sequence DP (LCS)Edit Distance, Longest Palindromic Subsequence, Longest Common SubstringMatch (1+dp[i-1][j-1]) vs Mismatch (max of dp[i-1][j], dp[i][j-1])
Interval DPMatrix Chain Multiplication, Palindromic Substrings, Burst BalloonsSplit into [i,k] and [k+1,j], combine cost
Grid DPUnique Paths, Minimum Path Sum, Dungeon GameFrom above + left (dp[i][j] = f(dp[i-1][j], dp[i][j-1]))
LIS (Patience Sorting)Longest Increasing Subsequence, Russian Doll Envelopes, MSTBinary search tail array to find position to place current element
Edit DistanceLevenshtein Distance, One Edit Distance, Minimum cost to make strings equalMatch (no cost) vs Insert/Delete/Replace (1 + min of adjacent states)

Key takeaways

1
DP is just recursion + memoization or bottom-up iteration. The patterns repeat.
2
You've seen the five core patterns
0/1 Knapsack, LCS, Unbounded Knapsack, Interval DP, Grid DP, LIS (patience), and Edit Distance.
3
Identifying the state and transition is 80% of the work. Write it down before coding.
4
Space optimization from O(N^2) to O(N) is a common follow-up
practice it on every pattern.
5
Print the DP table for small inputs to debug; it reveals off-by-one errors instantly.
6
Memorize the loop direction
backward for 0/1, forward for unbounded, length for interval.

Common mistakes to avoid

4 patterns
×

Failing to identify the base cases properly

Symptom
StackOverflow (top-down) or IndexOutOfBounds (bottom-up) when input is empty or single element.
Fix
Always write base cases first: dp[0] = 0 for empty state; for strings, dp[0][j] = j and dp[i][0] = i.
×

Not optimizing space when only the previous row/state is needed

Symptom
Memory usage spikes and candidate gets flagged for suboptimal solution.
Fix
Identify if transition only depends on previous row (e.g., knapsack, grid). Use 1D array and iterate backwards or forwards accordingly.
×

Over-complicating the state – using 4 dimensions when 2 suffice

Symptom
DP table is too large to compute in time, or code is unreadable.
Fix
Simplify subproblems: can you reduce state by swapping indices? E.g., in LCS, use dp[i][j] not dp[i][j][k]. Define state clearly on paper before coding.
×

Forgetting to handle capacity/amount sentinel for 'impossible' cases

Symptom
Returns Integer.MAX_VALUE or 0 instead of -1 when no solution exists.
Fix
Initialize with a large sentinel (e.g., amount+1) and check after fill: if dp[amount] > amount, return -1.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
Given an array of integers, find the maximum sum of a non-adjacent subse...
Q02SENIOR
Explain the difference between 0/1 Knapsack and Unbounded Knapsack. How ...
Q03SENIOR
You are given a 2D grid with obstacles. Find the number of unique paths ...
Q04SENIOR
Given a string s, find the longest palindromic subsequence. Explain the ...
Q01 of 04SENIOR

Given an array of integers, find the maximum sum of a non-adjacent subsequence. (House Robber variant). Walk through the recurrence and optimize space.

ANSWER
This is a classic DP problem. Define dp[i] = max sum up to house i. Recurrence: dp[i] = max(dp[i-1], dp[i-2] + nums[i]). Base: dp[0]=nums[0], dp[1]=max(nums[0], nums[1]). Space can be optimized to O(1) by keeping only prev and prevprev variables. For circular houses, run same process twice (skip first and last separately) and take max. `` public int rob(int[] nums) { if (nums.length == 0) return 0; int prev2 = 0, prev = 0; for (int num : nums) { int temp = prev; prev = Math.max(prev, prev2 + num); prev2 = temp; } return prev; } ``
FAQ · 4 QUESTIONS

Frequently Asked Questions

01
When should I use Top-Down vs. Bottom-Up DP?
02
How do I recognize a problem can be solved with Dynamic Programming?
03
What is the difference between 0/1 Knapsack and Unbounded Knapsack?
04
How do I optimize space in DP from 2D to 1D?
🔥

That's Coding Patterns. Mark it forged?

8 min read · try the examples if you haven't

Previous
Top 10 Tree Interview Problems
4 / 17 · Coding Patterns
Next
Top 10 Graph Interview Problems