Skip to main content

Dynamic Programming Fundamentals

DP State Transition Visualization

The Mental Model

Dynamic Programming is smart recursion. Instead of solving the same subproblem multiple times, we solve it once and remember the answer. The key insight: if you can express the answer to a problem in terms of answers to smaller problems, DP might work. Analogy: Imagine computing Fibonacci numbers by hand. To get F(50), you need F(49) and F(48). To get F(49), you need F(48) and F(47). Notice F(48) is needed twice—and it explodes from there. Without memoization, computing F(50) requires about 2^50 operations. With memoization (writing each answer on a sticky note the first time you compute it), it takes exactly 50 additions. That is the difference between exponential and linear: DP trades memory for time.
Pattern Recognition Signals:
  • “Count the number of ways”
  • “Find minimum/maximum cost”
  • “Can we achieve X?” (feasibility)
  • Problem has overlapping subproblems and optimal substructure
  • Constraints suggest O(n²) or O(n × m) is acceptable

The DP Mindset

When you see a DP problem, think through this framework:
1

Define the State

What information do I need to describe a subproblem?
  • dp[i] = answer for first i elements
  • dp[i][j] = answer for subproblem defined by i and j
2

Find the Transition

How does dp[i] relate to smaller subproblems?
  • dp[i] = f(dp[i-1], dp[i-2], …)
3

Identify Base Cases

What are the simplest subproblems with known answers?
4

Determine the Order

Which subproblems must be solved before others?
5

Extract the Answer

Where in the DP table is the final answer?

Pattern 1: Linear DP

State: dp[i] = answer considering first i elements.

Example: Fibonacci

Example: Climbing Stairs

Problem: n stairs, can climb 1 or 2 at a time. Count ways to reach top. State: dp[i] = number of ways to reach stair i Transition: dp[i] = dp[i-1] + dp[i-2] (come from i-1 or i-2)

Pattern 2: Coin Change (Unbounded Knapsack)

Problem: Given coins, find minimum coins to make amount. State: dp[i] = minimum coins to make amount i Transition: dp[i] = min(dp[i - coin] + 1) for each coin Worked example: coins = [1, 3, 4], amount = 6

Counting Ways

Problem: Count ways to make amount (combinations, not permutations). Critical subtlety: The loop order determines whether you count combinations or permutations.
  • Coins as outer loop (below): Each coin is considered once in order. Result: combinations. and are counted once.
  • Amount as outer loop: For each amount, try all coins. Result: permutations. , , and are counted separately.
Most problems ask for combinations. Read carefully!
Codeforces Problems:

Pattern 3: 0/1 Knapsack

Problem: n items with weight and value. Maximize value in capacity W. Each item used at most once. State: dp[i][w] = max value using first i items with capacity w Transition:
  • Don’t take item i: dp[i][w] = dp[i-1][w]
  • Take item i: dp[i][w] = dp[i-1][w - weight[i]] + value[i]
Why Backwards?: If we iterate forwards, we might use item i multiple times (since dp[w - weight[i]] is already updated). Backwards ensures we use the previous row’s values.

Pattern 4: Longest Increasing Subsequence (LIS)

Problem: Find length of longest strictly increasing subsequence. State: dp[i] = length of LIS ending at index i Transition: dp[i] = max(dp[j] + 1) for all j < i where arr[j] < arr[i]
Key Insight for O(n log n): Maintain the smallest possible tail for each LIS length. Use binary search to find where to place each element. Why this works: tails[i] stores the smallest ending element of all increasing subsequences of length i+1 found so far. This array is always sorted (a longer subsequence must end with a larger value). When a new element arrives, we binary search for where it fits: if it extends the longest subsequence, we append; otherwise, we replace an existing tail with a smaller value, “making room” for future extensions. Walked example: arr = [3, 1, 4, 1, 5, 9, 2, 6]
  • 3: tails = [3]
  • 1: tails = [1] (replace 3—a subsequence of length 1 ending at 1 is better)
  • 4: tails = [1, 4] (extends)
  • 1: tails = [1, 4] (1 is already in position)
  • 5: tails = [1, 4, 5] (extends)
  • 9: tails = [1, 4, 5, 9] (extends)
  • 2: tails = [1, 2, 5, 9] (replace 4—better potential for future extensions)
  • 6: tails = [1, 2, 5, 6] (replace 9)
  • LIS length = 4
Edge case: The tails array does NOT represent an actual LIS—it stores the best tails. To reconstruct the actual subsequence, you need to track parent pointers during the binary search updates.
Codeforces Problems:

Pattern 5: Longest Common Subsequence (LCS)

Problem: Find length of longest common subsequence of two strings. State: dp[i][j] = LCS length of s1[0..i-1] and s2[0..j-1] Transition:
  • 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])

Top-Down vs Bottom-Up

Rule of Thumb: Start with top-down (easier to write), convert to bottom-up if needed for optimization.
Contest tip: Top-down memoization is usually faster to code and less error-prone (you do not need to figure out loop order). Use it by default. Switch to bottom-up only when you need space optimization (rolling array) or the recursion depth would cause stack overflow.

Common Mistakes

Mistake 1: Wrong Base Case
Mistake 2: Integer Overflow
Mistake 3: Wrong Loop Order
  • For 0/1 Knapsack with space optimization: iterate capacity backwards
  • For unbounded knapsack: iterate capacity forwards
Mistake 4: Forgetting States If your DP gives wrong answers, you might be missing state dimensions:
  • Does order matter? → Might need dp[i][j]
  • Does parity matter? → Might need dp[i][0/1]

Practice Problems

Beginner (1100-1300)

Intermediate (1300-1500)

Advanced (1500-1700)


Key Takeaways

State = Subproblem

The DP state must uniquely identify a subproblem.

Transition = Recursion

The transition is how you’d write the recursive relation.

Base Case = Smallest

Base cases are the smallest subproblems with known answers.

Space Optimize

If dp[i] only depends on dp[i-1], you only need O(1) or O(n) space.

Next Up

Chapter 12: DP on Grids & Advanced

Master 2D DP, interval DP, and bitmask DP for more complex problems.