Dynamic Programming

03 Mins

Dynamic Programming (DP) is a technique for solving problems by breaking them into smaller subproblems, solving each subproblem once, and storing the result so we don’t have to calculate it again.

The core rule:

Solve smaller subproblems first, store their results, and combine them to solve the bigger problem.

Let’s see this in action using the classic 0/1 Knapsack Problem.


The Problem: Knapsack

dynamic-programming-knapsack

You carry a bag with a maximum capacity of 4 lbs.

A shop offers the following items:

ItemValue ($)Weight (lbs)
Guitar$15001 lb
Laptop$20003 lbs
Stereo$30004 lbs

Instead of testing all 2^n combinations, we build a 2D table, step by step.

  • Rows represent items considered so far.
  • Columns represent knapsack capacities from 0 to 4 lbs.
  • Each cell answers: What is the maximum value possible with this exact capacity using only the items available so far?

At each step, we check:

  1. If the item does not fit: Keep the previous best value for this capacity.
  2. If the item fits: Choose the better option between:
    • Leaving it: The best value without this item.
    • Taking it: The item’s value + the best value for the remaining weight left over.

Step 0: Base Case (No Items)

With 0 items, the maximum possible value is always 0:

Items considered ↓ / Capacity →01234
None00000

Step 1: Add the Guitar (1 lb, $1500)

The Guitar weighs 1 lb and is worth $1500.

Items Considered ↓ / Capacity →01234
None00000
Guitar01500150015001500

As soon as capacity reaches at least 1 lb, we can carry the Guitar. Because no other items are available yet, the value stays $1500 across all larger capacities.


Step 2: Add the Laptop (3 lbs, $2000)

Items Considered ↓ / Capacity →01234
None00000
Guitar01500150015001500
Laptop01500150020003500
  • At capacities 1 and 2, the Laptop (3 lbs) cannot fit, so we copy the previous best value ($1500).
  • At capacity 3, the Laptop fits. We compare:
    • Leave Laptop: $1500
    • Take Laptop: $2000 + value at remaining 0 lbs ($0) = $2000
    • max(1500, 2000) = 2000$.
  • At capacity 4:
    • Leave Laptop: $1500 (from previous row)
    • Take Laptop: $2000 + best value for remaining 1 lb (4 - 3 = 1) = 2000 + 1500 = 3500$
    • max(1500, 3500) = 3500.

Step 3: Add the Stereo (4 lbs, $3000)

Items Considered ↓ / Capacity →01234
None00000
Guitar01500150015001500
Laptop01500150020003500
Stereo01500150020003500

The Stereo only fits at capacity 4:

  • Leave Stereo: $3500 (Laptop + Guitar from row above)
  • Take Stereo: $3000 + best value for remaining 0 lbs (0) = 3000$
  • max(3500, 3000) = 3500$.

Our optimal value sits in the bottom-right cell: $3500 (Guitar + Laptop).


The Core Recurrence Relation

// If the item exceeds current capacity c:
dp[i][c] = dp[i - 1][c]

// If the item fits:
dp[i][c] = Math.max(
  dp[i - 1][c],                          // Leave it
  value + dp[i - 1][c - weight]          // Take it + remaining capacity best
)

In plain English:

Best value = max(leave current item, take current item + best value for whatever space remains)


Javascript Implementation


function knapsack(items, capacity) {
  const n = items.length;
  // Initialize a (n + 1) x (capacity + 1) matrix with 0s
  const dp = Array.from({ length: n + 1 }, () => Array(capacity + 1).fill(0));

  for (let i = 1; i <= n; i++) {
    const { weight, value } = items[i - 1];

    for (let c = 1; c <= capacity; c++) {
      if (weight > c) {
        // Item is too heavy; carry forward previous optimal value
        dp[i][c] = dp[i - 1][c];
      } else {
        // Choose the maximum between taking or leaving the item
        dp[i][c] = Math.max(
          dp[i - 1][c],
          value + dp[i - 1][c - weight]
        );
      }
    }
  }

  return dp[n][capacity];
}

const items = [
  { weight: 1, value: 1500 }, // Guitar
  { weight: 3, value: 2000 }, // Laptop
  { weight: 4, value: 3000 }  // Stereo
];

console.log(knapsack(items, 4)); // Output: 3500