All topics·Topic 08 / 09
08Dynamic Programming

Dynamic Programming Algorithms

Memoization, tabulation, and optimization

Theory available
1 problems
Difficulty split
Easy
0
Med
1
Hard
0
In-depth guides:Dynamic Programming

Dynamic Programming - Complete Guide

Introduction

Dynamic Programming (DP) is a powerful algorithmic technique used to solve complex problems by breaking them down into simpler subproblems. It stores the results of these subproblems to avoid redundant calculations, transforming problems with exponential time complexity into polynomial time. This technique is a staple in coding interviews and is widely used in fields requiring optimization, such as economics, bioinformatics, and artificial intelligence.

Key Characteristics

  • Overlapping Subproblems: The problem can be broken down into overlapping subproblems, meaning the same subproblems are solved multiple times.

  • Optimal Substructure: The optimal solution to the main problem depends on the optimal solutions to its subproblems.

  • Memoization: A top-down approach where results of expensive function calls are cached to avoid re-computation.

  • Tabulation: A bottom-up approach where the solution is built iteratively, starting from the base cases.

  • State Transition: The ability to define how the current state depends on previous states (recurrence relation).

Core Techniques

1. Top-Down Memoization

A recursive approach where you solve the problem by breaking it down and storing the results of subproblems in a data structure (usually a hash map or array) to prevent redundant calculations.

Key Applications:

  • Fibonacci Sequence

  • Computing Factorials

  • Recursive Tree Traversal with Caching

Learn more: Top-Down Memoization →

2. Bottom-Up Tabulation

An iterative approach where you solve the problem by filling up a table (usually a 2D array or 1D array) based on the results of smaller subproblems, starting from the base cases.

Key Applications:

  • Longest Common Subsequence

  • Edit Distance

  • Coin Change Problem

Learn more: Bottom-Up Tabulation →

3. Space Optimization

Reducing the space complexity of a DP solution by realizing that you often only need the results of the previous step or a few previous steps to compute the current one.

Key Applications:

  • Fibonacci Number (O(1) space)

  • Knapsack Problem (1D array)

  • Jump Game II

Learn more: Space Optimization →

4. 0/1 Knapsack

A classic DP problem where you have a set of items with weights and values, and a knapsack with a weight limit. You must maximize the total value without exceeding the weight limit.

Key Applications:

  • Resource Allocation

  • Budget Planning

  • Loading Problems

Learn more: 0/1 Knapsack →

Code Examples

Example 1: Fibonacci Sequence (Tabulation)

function fibonacci(n: number): number {
  if (n <= 1) return n;

  const dp = new Array(n + 1).fill(0);
  dp[0] = 0;
  dp[1] = 1;

  for (let i = 2; i <= n; i++) {
    dp[i] = dp[i - 1] + dp[i - 2];
  }

  return dp[n];
}

Example 2: Climbing Stairs (Space Optimized)

function climbStairs(n: number): number {
  if (n <= 2) return n;

  let prev2 = 1; // dp[i-2]
  let prev1 = 2; // dp[i-1]

  for (let i = 3; i <= n; i++) {
    const current = prev1 + prev2;
    prev2 = prev1;
    prev1 = current;
  }

  return prev1;
}

Example 3: Longest Common Subsequence (2D Tabulation)

function longestCommonSubsequence(text1: string, text2: string): number {
  const m = text1.length;
  const n = text2.length;
  const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));

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

Time and Space Complexity Guide

  • *O(N M):** The most common complexity for DP problems involving two dimensions (e.g., 2D grid, LCS). N and M represent the dimensions of the input.

  • O(N): Common for 1D DP problems (e.g., Fibonacci, Climbing Stairs) where the state depends only on the previous step.

  • O(1): Achievable with space optimization techniques where only a few variables are needed to track the state.

  • Space Complexity: Typically O(N * M) for standard tabulation. Can be reduced to O(N) or O(1) with optimization.

Learning Path

Beginner

  • Fibonacci Number

  • Climbing Stairs

  • House Robber

  • Max Subarray Sum

Intermediate

  • Longest Increasing Subsequence (LIS)

  • Edit Distance

  • Coin Change Problem

  • Knapsack Problem

Advanced

  • Matrix Chain Multiplication

  • Optimal Binary Search Tree (OBST)

  • Partition DP (Palindrome Partitioning)

  • Regular Expression Matching

Practice Strategy

  1. Identify the State: Clearly define what the subproblem represents (e.g., dp[i] is the answer for the first i elements).

  2. Find the Recurrence Relation: Write down the mathematical equation that connects the current state to previous states.

  3. Choose the Approach: Decide between Top-Down (Memoization) for easier recursion implementation or Bottom-Up (Tabulation) for better performance and avoiding stack overflow.

  4. Optimize Space: After solving the problem, review the solution to see if you can reduce the space complexity by discarding unnecessary states.

Common Mistakes to Avoid

  • Not Handling Base Cases: Forgetting to initialize the first few states (e.g., dp[0] or dp[1]) leads to incorrect results.

  • Incorrect Recurrence Relation: Writing the logic that connects states incorrectly (e.g., using dp[i] = dp[i] + 1 instead of dp[i] = dp[i-1] + 1).

  • Stack Overflow: Using recursion without memoization for problems with deep recursion stacks (e.g., Fibonacci with large N).

  • Misinterpreting the State: Defining the state incorrectly (e.g., using dp[i][j] to represent the answer for the substring from i to j instead of 0 to i and 0 to j).

  • Ignoring Overlapping Subproblems: Trying to solve a problem recursively without memoization when the subproblems overlap significantly, resulting in exponential time complexity.

Real-World Applications

  • Economics: Resource allocation, portfolio optimization, and maximizing profit over time.

  • Bioinformatics: DNA sequencing, protein folding, and aligning genetic sequences.

  • Artificial Intelligence: Reinforcement Learning (Q-Learning) and pathfinding algorithms.

  • Networking: Routing algorithms and traffic flow optimization.

  • Game Theory: Solving games like Chess or Go by evaluating board states.

Summary

Dynamic Programming is a fundamental concept that separates novice programmers from experts. By mastering the ability to break down problems into overlapping subproblems and store their solutions, you can tackle complex optimization challenges efficiently. Consistent practice with different DP patterns is the key to internalizing this technique.