Skip to content
← Back to Blog

Dynamic Programming Patterns Explained: A Visual Guide

10 min read

Dynamic Programming (DP) has a reputation for being the hardest topic in coding interviews. But here is a secret: most DP problems fall into one of a few patterns. Once you learn these patterns, DP becomes much more approachable. You stop asking "how do I solve this DP problem?" and start asking "which DP pattern does this problem follow?"

This guide covers the 6 core DP patterns available in AlgoArk. For each, we explain the idea, the key recurrence, an example problem, and how to identify it.

1. Fibonacci Pattern

The simplest DP pattern. Each state depends only on the previous 1-2 states. If you can define your problem as dp[i] = f(dp[i-1], dp[i-2]), you are looking at the Fibonacci pattern. Because each state only looks back a fixed number of steps, you can optimize space to O(1) by keeping just two variables.

Key recurrence: dp[i] = dp[i-1] + dp[i-2] (or max/min of previous states)

Example problem: Climbing Stairs — How many ways to reach step n if you can climb 1 or 2 steps at a time?

Also: House Robber, Decode Ways, Fibonacci Number

// Climbing Stairs
function climbStairs(n: number): number {
  let prev2 = 1, prev1 = 1;
  for (let i = 2; i <= n; i++) {
    const curr = prev1 + prev2;
    prev2 = prev1;
    prev1 = curr;
  }
  return prev1;
}

View Fibonacci Pattern details →

2. 0/1 Knapsack

You have N items, each with a weight and a value. You have a knapsack with capacity W. For each item, you make a binary decision: take it or skip it. The goal is to maximize the total value without exceeding the capacity. This pattern generalizes to any problem where you choose a subset under a constraint.

Key recurrence: dp[w] = max(dp[w], dp[w - weight[i]] + value[i]) — iterate weights in reverse to avoid using an item twice

Example problem: Partition Equal Subset Sum — Can you divide an array into two subsets with equal sums? (This is a 0/1 Knapsack where the capacity is totalSum / 2.)

Also: Target Sum, Last Stone Weight II, 0/1 Knapsack

// 0/1 Knapsack (1D optimization)
function knapsack(weights: number[], values: number[], W: number): number {
  const dp = new Array(W + 1).fill(0);
  for (let i = 0; i < weights.length; i++) {
    for (let w = W; w >= weights[i]; w--) {  // reverse!
      dp[w] = Math.max(dp[w], dp[w - weights[i]] + values[i]);
    }
  }
  return dp[W];
}

View 0/1 Knapsack Pattern details →

3. Unbounded Knapsack

Like 0/1 Knapsack, but each item can be used unlimited times. The classic example is Coin Change: given coin denominations, find the minimum number of coins to make an amount. The key difference from 0/1 Knapsack is that you iterate the weight/amount in the forward direction, allowing the same item to be picked multiple times.

Key recurrence: dp[a] = min(dp[a], dp[a - coin] + 1) — iterate amounts in forward direction

Example problem: Coin Change — Given coins [1, 5, 10, 25], what is the minimum number of coins to make 37 cents?

Also: Coin Change II (count ways), Rod Cutting, Perfect Squares

// Coin Change (minimum coins)
function coinChange(coins: number[], amount: number): number {
  const dp = new Array(amount + 1).fill(Infinity);
  dp[0] = 0;
  for (const coin of coins) {
    for (let a = coin; a <= amount; a++) {  // forward!
      dp[a] = Math.min(dp[a], dp[a - coin] + 1);
    }
  }
  return dp[amount] === Infinity ? -1 : dp[amount];
}

View Unbounded Knapsack Pattern details →

4. Longest Common Subsequence (LCS)

The LCS pattern compares two sequences using a 2D DP table. Each cell dp[i][j] represents the optimal answer for the first i characters of string 1 and the first j characters of string 2. At each cell, you either match the current characters or skip one from either string. This pattern covers a wide range of two-string comparison problems.

Key recurrence: If s1[i-1] == s2[j-1]: dp[i][j] = dp[i-1][j-1] + 1. Else: dp[i][j] = max(dp[i-1][j], dp[i][j-1])

Example problem: Edit Distance — Given two words, find the minimum number of insertions, deletions, and substitutions to convert one to the other.

Also: Longest Common Subsequence, Minimum ASCII Delete Sum, Longest Palindromic Subsequence

// Longest Common Subsequence
function lcs(s1: string, s2: string): number {
  const m = s1.length, n = s2.length;
  const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));

  for (let i = 1; i <= m; i++) {
    for (let j = 1; j <= n; j++) {
      if (s1[i - 1] === s2[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];
}

View LCS Pattern details →

5. DP on Trees

When a problem combines a tree structure with optimization (maximum path sum, tree diameter, etc.), you apply DP using post-order DFS. Each node computes its answer based on the answers of its children. The trick is that you often track two things: the best answer that can be extended upward (returned to the parent) and the best answer that passes through this node (updates a global result but cannot be extended further).

Key recurrence: return max(leftGain, rightGain) + node.val to parent; update global with leftGain + rightGain + node.val

Example problem: Binary Tree Maximum Path Sum — Find the maximum sum path in a binary tree where the path does not need to go through the root.

Also: House Robber III, Diameter of Binary Tree, Longest Path With Different Adjacent Characters

// Binary Tree Maximum Path Sum
let globalMax = -Infinity;

function maxPathDown(node: TreeNode | null): number {
  if (!node) return 0;
  const left = Math.max(0, maxPathDown(node.left));
  const right = Math.max(0, maxPathDown(node.right));

  // Path through this node (cannot extend further up)
  globalMax = Math.max(globalMax, left + right + node.val);

  // Best single-side path (can extend to parent)
  return Math.max(left, right) + node.val;
}

View DP on Trees Pattern details →

6. Grid DP

Grid DP problems give you a 2D matrix and ask for the optimal way to traverse it — usually from the top-left to the bottom-right. Each cell's value depends on the cell above it and the cell to its left. These problems are visually intuitive because you can literally see the DP table on the grid itself.

Key recurrence: dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1]) (for minimum path sum)

Example problem: Minimum Path Sum — Given a grid of non-negative integers, find a path from top-left to bottom-right that minimizes the sum.

Also: Unique Paths, Unique Paths II, Maximal Square, Dungeon Game

// Minimum Path Sum
function minPathSum(grid: number[][]): number {
  const m = grid.length, n = grid[0].length;
  const dp = Array.from({ length: m }, () => new Array(n).fill(0));

  dp[0][0] = grid[0][0];
  for (let i = 1; i < m; i++) dp[i][0] = dp[i-1][0] + grid[i][0];
  for (let j = 1; j < n; j++) dp[0][j] = dp[0][j-1] + grid[0][j];

  for (let i = 1; i < m; i++) {
    for (let j = 1; j < n; j++) {
      dp[i][j] = grid[i][j] + Math.min(dp[i-1][j], dp[i][j-1]);
    }
  }
  return dp[m-1][n-1];
}

View Grid DP Pattern details →

How to Identify Which DP Pattern to Use

When you encounter a DP problem, ask yourself these questions:

Does the current state depend on the previous 1-2 states? Fibonacci Pattern

Do you choose to take or skip each item, with a capacity constraint? 0/1 Knapsack

Can items be reused unlimited times? Unbounded Knapsack

Are you comparing two strings/sequences? LCS Pattern

Is the input a tree with an optimization goal? DP on Trees

Is the input a 2D grid with a traversal goal? Grid DP

Common DP Mistakes

  • Confusing 0/1 and Unbounded Knapsack: The difference is whether items can be reused. In code, 0/1 iterates weights in reverse; unbounded iterates forward.
  • Using DP when Greedy works: Not all optimization problems need DP. If the problem has a greedy choice property (locally optimal = globally optimal), use Greedy instead.
  • Forgetting base cases: Every DP solution needs clear base cases. For Fibonacci: dp[0] = dp[1] = 1. For Knapsack: dp[0] = 0. Missing or wrong base cases break everything.
  • Not optimizing space: Many 2D DP solutions can be reduced to 1D because each row only depends on the previous row. This is a common follow-up question in interviews.

Study Order

If you are starting from scratch, study the DP patterns in this order:

  1. Fibonacci — Build DP intuition with the simplest pattern
  2. Grid DP — Visually intuitive; see the DP table on the grid
  3. 0/1 Knapsack — The foundation of subset-selection problems
  4. Unbounded Knapsack — Small twist on 0/1; master the forward vs. reverse distinction
  5. LCS — Two-sequence problems with 2D DP
  6. DP on Trees — Combines tree traversal with DP; the most advanced pattern

For each pattern, solve 3-5 problems before moving on. Use our Decision Tree to check if you are identifying the right pattern, and the Quiz to test your recognition skills.

Related reads:

Explore all 29 DSA patterns

DP is just one category. AlgoArk covers 29 patterns across 7 categories with animated walkthroughs.