DSA

Part 16 — Dynamic Programming

A practical, interview-focused guide to Dynamic Programming in Python: state design, recurrence relations, memoization, tabulation, optimization, invariants, complexity, and Staff-level interview reasoning.

Deepak Mishra19 min read


Dynamic Programming (DP) is one of the most important advanced algorithmic techniques for Staff and Principal-level coding interviews.

The source places Chapters 15–18 in the advanced-algorithm section and identifies dynamic programming alongside recursion, greedy algorithms, and graph modeling.

The source’s central DP model is:

Problem

Can I define a smaller state?

state[i]

state[i] depends on previous states

cache states

For Staff/Principal interviews, the source recommends asking:

1. What is the state?
2. What does the state represent?
3. What is the transition?
4. What are the base cases?
5. What is the dependency graph?
6. Can memory be reduced?

1. What Dynamic Programming Is Really Teaching

Do not think:

“DP means using a 2-D array.”

That is an implementation detail.

The deeper idea is:

Break a problem into states

Solve each state once

Reuse those answers

Avoid repeated work

So the fundamental DP question is:

What information completely describes a subproblem?

That information is your state.


2. The Core DP Pattern

Most DP problems can be reduced to:

State

Transition

Base cases

Evaluation order

Final answer

For example:

dp[i] = best answer for the first i elements

Then determine:

dp[i] depends on which earlier states?

That dependency defines the transition.


3. Why DP Is Needed

Consider recursive Fibonacci:

def fib(n):
    if n <= 1:
        return n

    return fib(n - 1) + fib(n - 2)

The recursion repeatedly computes the same states.

For example:

fib(5)
├── fib(4)
│   ├── fib(3)
│   └── fib(2)
└── fib(3)
    ├── fib(2)
    └── fib(1)

fib(3) and fib(2) are computed multiple times.

The bottleneck is:

Repeated subproblems

DP removes that repeated work.


4. DP = Recursion + Reuse

A useful mental model is:

Recursion

Repeated subproblems

Cache results

Memoization

Dynamic Programming

This is directly aligned with the source’s description of DP as solving smaller instances and caching their solutions for performance.


5. Two Major DP Styles

There are two standard approaches:

Dynamic Programming
       |
       +----------------+
       |                |
       v                v
  Top-down          Bottom-up
  Memoization       Tabulation

Top-down

Start with the original problem and recursively solve states as needed.

Bottom-up

Start with the smallest states and build toward the final answer.

Both represent the same underlying state-transition relationship.


6. Top-Down DP — Memoization

Example:

from functools import lru_cache

@lru_cache(maxsize=None)
def fib(n):
    if n <= 1:
        return n

    return fib(n - 1) + fib(n - 2)

The cache stores:

fib(0)
fib(1)
fib(2)
...
fib(n)

Each state is computed once.

Complexity:

Time:  O(n)
Space: O(n)

The space includes the cached states and recursive call stack.


7. Bottom-Up DP — Tabulation

The same Fibonacci problem can be written iteratively:

def fib(n):
    if n <= 1:
        return n

    dp = [0] * (n + 1)
    dp[1] = 1

    for i in range(2, n + 1):
        dp[i] = dp[i - 1] + dp[i - 2]

    return dp[n]

Now the dependency order is explicit:

dp[0]

dp[1]

dp[2]

dp[3]

...
dp[n]

8. The Most Important DP Question: What Does dp[i] Mean?

Never write:

dp[i] = ...

before defining what dp[i] represents.

A strong interview statement is:

dp[i] represents the optimal answer for the problem restricted to the first i elements.”

Or:

dp[i] represents the number of valid ways to reach state i.”

Or:

dp[i] represents the minimum cost required to reach position i.”

The meaning of the state determines the entire algorithm.


9. State Design

A DP state should contain exactly the information needed to determine the future.

Too little information:

Cannot determine the next transition.

Too much information:

Unnecessary number of states
→ more memory
→ more computation

Staff-level DP is therefore largely a state-design problem.

Ask:

What decisions have already been made?
What information affects future decisions?
Can I discard everything else?

10. One-Dimensional DP

Typical state:

dp[i]

Examples:

minimum cost to reach i
maximum value through i
number of ways to reach i
best solution using first i items

General structure:

dp[0] = base_case

for i in range(1, n + 1):
    dp[i] = transition(dp, i)

return dp[n]

11. Example — Climbing Stairs

Suppose you can climb either:

1 step
or
2 steps

Let:

dp[i] = number of ways to reach step i

To reach i, the previous step must be:

i - 1
or
i - 2

Therefore:

dp[i] = dp[i - 1] + dp[i - 2]

Python:

def climb_stairs(n):
    if n <= 1:
        return 1

    prev2 = 1
    prev1 = 1

    for _ in range(2, n + 1):
        current = prev1 + prev2
        prev2 = prev1
        prev1 = current

    return prev1

The full DP array is not necessary.

This demonstrates space optimization.


12. State Compression

Suppose:

dp[i] depends only on dp[i-1] and dp[i-2]

Then storing every state may be unnecessary.

Instead:

previous two states

current state

So:

O(n) space

can become:

O(1) auxiliary space

The source explicitly asks Staff/Principal candidates to consider whether DP memory can be reduced.


13. Two-Dimensional DP

Sometimes one variable is insufficient.

State may become:

dp[i][j]

Typical interpretation:

dp[i][j] =
best answer using first i items
under condition/capacity j

Examples include:

grid problems
knapsack-style problems
sequence alignment
two-string problems
interval problems

The key is still the same:

Define state
→ define transition
→ define base cases

14. Grid DP

Imagine:

1 1 1
1 1 1
1 1 1

Suppose you can move:

right
down

Define:

dp[i][j]
=
number of ways to reach cell (i,j)

Then:

dp[i][j]
=
dp[i-1][j]
+
dp[i][j-1]

because the last move must come from either:

above
or
left

This is the classic DP pattern:

Current state
    =
sum/choice of predecessor states

15. DP Dependency Graph

A powerful Staff-level visualization is:

dp[i]
 /   \
dp[i-1] dp[i-2]

For 2-D:

        dp[i][j]
        /      \
dp[i-1][j]   dp[i][j-1]

The dependency graph answers:

In what order must I compute the states?

If dp[i] depends on earlier states, a forward iteration may work.

If it depends on future states, reverse iteration or another evaluation order may be required.


16. Base Cases

Base cases are not merely stopping conditions.

They establish the smallest known states.

For example:

dp[0] = 0

might mean:

The cost of solving an empty problem is zero.

Or:

dp[0] = 1

might mean:

There is exactly one way to construct an empty sequence.

The correct base case depends on the semantic definition of the state.


17. Transition Function

Once the state is defined, ask:

How can the optimal solution for this state be constructed from smaller states?

Examples:

dp[i] = dp[i-1] + dp[i-2]

dp[i] = min(dp[i-1] + cost[i],
            dp[i-2] + cost[i])

dp[i] = max(dp[i-1],
            value[i] + dp[previous])

The transition is the mathematical heart of the DP solution.


18. Optimization vs Counting

DP states often fall into different categories.

Counting

How many ways?

Usually:

sum of possibilities

Optimization

What is the minimum/maximum?

Usually:

min(...)
or
max(...)

Feasibility

Is it possible?

Usually:

True / False

Recognizing which category you have helps determine the transition.


19. DP Pattern: Minimum Cost

Suppose:

dp[i] = minimum cost to reach i

Then a transition might be:

dp[i] = min(
    dp[i - 1] + cost1,
    dp[i - 2] + cost2
)

The key reasoning is:

Every valid way to reach i
must come from one of the allowed predecessor states.

Then choose the minimum.


20. DP Pattern: Maximum Value

Suppose:

dp[i] = maximum value achievable through i

A transition may be:

dp[i] = max(
    dp[i - 1],
    value[i] + dp[previous]
)

The two branches often correspond to:

don't take current item
take current item

This include/exclude structure appears frequently in optimization DP.


21. DP Pattern: Feasibility

Suppose:

dp[i] = whether state i is reachable

Then:

dp[i] = dp[i - 1] or dp[i - 2]

This is a Boolean DP.

The same framework still applies:

state
+
transition
+
base cases

22. Memoization vs Tabulation

Feature Memoization Tabulation
Direction Top-down Bottom-up
Implementation Recursion + cache Iteration + table
Computes Needed states Usually all states
Stack usage Yes No recursive stack
Natural for Recursive formulation Explicit dependencies
Space optimization Sometimes harder Often easier

Neither is universally better.

The correct choice depends on:

state graph
+
number of reachable states
+
recursion depth
+
memory constraints
+
implementation clarity

23. DP and Recursion

The relationship can be visualized as:

Recursion
    |
    v
Define subproblem
    |
    v
Repeated subproblem?
   / \
 NO   YES
 |     |
 v     v
Keep   Cache
recursion
         |
         v
    Memoization
         |
         v
         DP

This is the conceptual bridge from Part 15 to Part 16.


24. How to Detect a DP Problem

Look for these signals:

1. The problem has smaller subproblems.

2. The same subproblem can appear multiple times.

3. The answer depends on previous decisions/states.

4. The problem asks for:
   - minimum
   - maximum
   - number of ways
   - feasibility

5. Brute-force recursion is exponential because of repeated work.

The source’s pattern map summarizes this recognition as:

Repeated subproblems

Dynamic programming

25. DP vs Divide and Conquer

This distinction is important.

Divide and Conquer

Subproblems are generally independent:

A       B
 \     /
  combine

Dynamic Programming

Subproblems overlap:

      A
     / \
    B   C
     \ /
      D

The repeated state D should be solved once and reused.

So:

Independent subproblems
→ Divide & Conquer

Overlapping subproblems
→ Dynamic Programming

26. DP and Optimal Substructure

A useful DP problem usually has optimal substructure:

An optimal solution can be constructed from optimal solutions to relevant smaller subproblems.

But don’t simply say:

“It has optimal substructure.”

Explain the actual relationship.

For example:

If the optimal solution reaches i from i-1,
then the prefix ending at i-1 must itself use an optimal solution;
otherwise we could replace it with a better one.

That is the reasoning an interviewer wants to hear.


27. DP Correctness Through Invariants

For bottom-up DP, state the invariant.

Example:

Before processing i,
dp[j] is already the correct answer for every completed state j < i.

After calculating:

dp[i]

the invariant becomes:

dp[j] is correct for every j <= i.

This is a clean correctness argument.

The source identifies invariants as one of the most valuable interview concepts and recommends using properties that remain true to rule out incorrect solutions.


28. Brute Force → DP

A strong interview progression is:

Brute force

Correctness

Complexity

Identify repeated work

Define state

Memoize

Optimize memory

The source explicitly recommends iterative refinement:

Brute Force

Correctness

Complexity

Bottleneck

Observation

Optimization

Optimal solution

29. Example of the DP Evolution

Start with recursion:

def fib(n):
    if n <= 1:
        return n

    return fib(n - 1) + fib(n - 2)

Problem:

Exponential repeated computation

Add memoization:

def fib(n, memo):
    if n <= 1:
        return n

    if n in memo:
        return memo[n]

    memo[n] = fib(n - 1, memo) + fib(n - 2, memo)
    return memo[n]

Then convert to bottom-up:

def fib(n):
    if n <= 1:
        return n

    a, b = 0, 1

    for _ in range(2, n + 1):
        a, b = b, a + b

    return b

Evolution:

Exponential recursion

Memoization

Linear DP

Constant auxiliary space

That evolution is an excellent Staff-level explanation.


30. Space Optimization

Suppose:

dp[i] depends only on:
dp[i-1]
dp[i-2]

Then:

full table
O(n)

can become:

two variables
O(1)

Always ask:

Does the final answer require the entire DP table, or only a small frontier of states?

This question can make a major difference for large inputs.


31. When You Need the Full DP Table

Do not optimize memory blindly.

You may need the complete table when:

you must reconstruct the solution
you need arbitrary historical states
future queries need previous states
the transition depends on many earlier states

For example:

best value

may need only the final state, while:

actual selected items

may require additional information for reconstruction.


32. DP Reconstruction

Sometimes the problem asks:

“What is the optimal value?”

But sometimes it asks:

“Which choices produce the optimal value?”

Then maintain:

DP value
+
decision information

Conceptually:

dp[i] = best value
choice[i] = decision producing dp[i]

Then reconstruct:

final state

choice

previous state

choice

...

This turns DP from:

optimization

into:

optimization + reconstruction

33. Multi-Dimensional State

Sometimes:

dp[i]

is not enough.

You may need:

dp[i][j]

or:

dp[i][j][k]

The Staff-level question is:

Which dimensions are genuinely necessary to distinguish future decisions?

Do not add dimensions simply because the problem looks complicated.

Every additional dimension increases:

number of states
+
memory
+
runtime

34. DP State Explosion

Suppose:

n possible values
m possible capacities

Then:

O(nm)

states may be manageable.

But:

n × m × k

becomes:

O(nmk)

The state design therefore directly controls scalability.

For Staff interviews, explicitly discuss:

How many states exist?
How much work per state?
How much memory per state?

35. DP Complexity Formula

A useful general formula is:

Total time
=
number of states
×
transition cost per state

And:

Total space
=
number of stored states
+
reconstruction information
+
call stack

For example:

n states
×
O(1) transition
=
O(n)

Or:

n × m states
×
O(1) transition
=
O(nm)

This is much more useful than memorizing individual DP problems.


36. DP Interview Checklist

Before coding:

What is the state?

What exactly does dp[state] mean?

What decisions lead into this state?

What is the transition?

What are the base cases?

What is the evaluation order?

How many states exist?

How much work per state?

Can I reduce memory?

Do I need reconstruction?

The source explicitly recommends these state, transition, base-case, dependency, and memory questions for Staff/Principal candidates.


37. Common DP Mistakes

Mistake 1 — Starting with the table

Don’t start with:

dp = [[0] * ...]

Start with the state definition.


Mistake 2 — Undefined state semantics

If you cannot finish:

dp[i] represents…”

you probably haven’t solved the problem yet.


Mistake 3 — Wrong base case

The transition may be mathematically correct but still fail because the smallest states are wrong.


Mistake 4 — Missing a state dimension

If future decisions depend on another variable, that information may need to be part of the state.


Mistake 5 — Over-sized state

Unnecessary dimensions can make the algorithm much slower.


Mistake 6 — Confusing DP with brute-force caching

Memoization helps only if the state definition correctly identifies equivalent subproblems.


Mistake 7 — Ignoring output requirements

If the interviewer asks for the actual sequence/choices, returning only the optimal value is incomplete.


38. DP vs Greedy

This is an important transition to Part 17.

Dynamic Programming

Explore multiple possible states

Compare their outcomes

Reuse results

Greedy

Choose locally best option

Never reconsider

Need proof that choice is globally safe

The source emphasizes exactly this Staff-level distinction: greedy requires justification that the local choice is globally safe.

So:

DP
→ compare alternatives systematically

Greedy
→ prove one alternative is safe

39. DP Decision Tree

Can the problem be expressed
using smaller states?
          |
         YES

Are subproblems repeated?
       /          \
     NO            YES
     |              |
     v              v
Divide &         Dynamic
Conquer          Programming
                    |
              +-----+-----+
              |           |
              v           v
         Memoization   Tabulation

Then ask:

Can the state be compressed?
        |
       YES

Reduce memory

40. Staff-Level DP Reasoning

At Staff level, don’t say:

“This is a DP problem.”

Say:

“The brute-force recursion recomputes the same subproblems. I can define a state that completely captures the information relevant to future decisions. Each state depends on a small set of previously solved states, so I can compute each state once. Then I’ll check whether the dependency structure allows memory compression.”

That communicates:

problem diagnosis
+
state design
+
transition
+
complexity
+
optimization

41. Pattern + Constraints

The source strongly emphasizes:

Pattern
+
Constraints
+
Requirements
=
Algorithm choice

For DP, ask:

How large is the state space?

Can I afford O(n) memory?

Can I afford O(nm)?

Do I need the full table?

Is recursion depth safe?

Can states be compressed?

Do I need online/streaming processing?

Do not choose DP merely because a problem has recursion.


42. DP and Streaming

If the state depends only on a small recent window:

dp[i]
depends on
dp[i-1], dp[i-2]

then the algorithm can often process the input incrementally.

Conceptually:

stream

current state

small DP frontier

next state

This is particularly valuable when input is too large to retain entirely in memory.

The source highlights streaming algorithms as important for senior-level reasoning and describes them as processing input sequences with limited memory.


43. DP as a State Machine

A powerful mental model is:

        +--------+
        | State  |
        +--------+
          /    \
         /      \
        v        v
   transition  transition
       |            |
       v            v
    State          State

The DP table is essentially a record of the best/valid/count value associated with each state.

This perspective becomes especially useful for:

sequence problems
grid problems
resource allocation
scheduling
state-machine problems

44. A Universal DP Template

Top-down:

def solve(state):
    if state in memo:
        return memo[state]

    if is_base_case(state):
        return base_value(state)

    answer = combine(
        solve(next_state_1),
        solve(next_state_2)
    )

    memo[state] = answer
    return answer

Bottom-up:

dp = initialize_base_cases()

for state in evaluation_order:
    dp[state] = transition(dp, state)

return dp[target]

The exact implementation changes, but the reasoning framework stays the same.


45. DP Complexity Template

When explaining complexity, say:

There are S distinct states.

Each state takes O(T) time to evaluate.

Therefore:
Time = O(S × T)

If I store all states:
Space = O(S)

If I only retain the dependency frontier:
Space may be reduced.

This is an excellent reusable Staff-level explanation.


46. DP Master Flow

Problem

Brute force

Identify repeated work

Define state

Define state semantics

Find transition

Define base cases

Build dependency graph

Memoization / tabulation

Analyze state count

Optimize memory

Test edge cases

Explain trade-offs

47. Final DP Cheat Sheet

Concept Question to Ask
State What uniquely defines the subproblem?
State meaning What exactly does dp[state] represent?
Transition How is this state built from smaller states?
Base case What is the smallest known state?
Dependency Which states must already be computed?
Memoization Are states being recomputed?
Tabulation Can I compute states in dependency order?
Complexity How many states × work per state?
Space Do I need every state?
Reconstruction Do I need the actual decisions?
Optimization Can the DP frontier be compressed?

48. The One Formula to Remember

For most DP problems:

DP complexity
=
number of distinct states
×
work per state

And the most important design rule is:

State must contain
exactly the information
needed to determine the future.

49. Final Takeaway

Dynamic Programming is not primarily about arrays called dp.

It is about recognizing repeated subproblems and turning them into reusable states.

Remember:

Brute Force

Repeated Work

State

Transition

Base Cases

Memoization / Tabulation

State Compression

The six questions to memorize are:

1. What is the state?

2. What does the state mean?

3. What is the transition?

4. What are the base cases?

5. What is the dependency graph?

6. Can I reduce memory?

These are exactly the questions highlighted in the source for Staff/Principal DP reasoning.


50. Part 15 → Part 16 → Part 17

The algorithmic progression is:

Part 15 — Recursion

Define smaller problems

Part 16 — Dynamic Programming

Reuse overlapping subproblems

Part 17 — Greedy Algorithms

Prove a local choice is globally safe

Part 18 — Graphs

Model relationships and connectivity

The deeper progression is:

Recursion
→ Decompose

DP
→ Remember

Greedy
→ Prove

Graphs
→ Model

For Staff/Principal interviews, the goal is not to memorize DP patterns mechanically.

The goal is to be able to look at an unfamiliar problem and say:

“I can define the state, explain why that state is sufficient, derive the transition, prove the recurrence, calculate the number of states, identify repeated work, and determine whether the solution can be compressed or replaced by a simpler technique.”

That is the real Dynamic Programming skill.