13.1 DP State Design

State design is the most important step in dynamic programming.

13.1 DP State Design

State design is the most important step in dynamic programming. Once the state is correct, the recurrence relation often becomes obvious. When the state is poorly designed, every subsequent step becomes difficult: transitions become complicated, memory usage grows unnecessarily, correctness proofs become fragile, and optimization opportunities disappear.

Many beginners focus immediately on finding recurrence relations. Experienced algorithm designers spend most of their effort defining the state itself. A well-designed state captures exactly the information required to solve a subproblem and nothing more.

Problem

Given a problem that exhibits overlapping subproblems and optimal substructure, how do we design a dynamic programming state that leads to an efficient and correct solution?

Understanding What a State Represents

A dynamic programming state represents a subproblem.

For example, consider computing Fibonacci numbers:

$$ F(n)=F(n-1)+F(n-2) $$

A natural state is:

$$ dp[i] $$

where:

dp[i] = the value of the ith Fibonacci number.

Each state corresponds to a smaller version of the original problem.

The key observation is that a state should answer a meaningful question.

Bad state:

dp[i] = something related to i

Good state:

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

The state description should be understandable without reading the recurrence.

Recipe 1: Define the Final Answer First

Before creating any state, identify the final answer.

Suppose we want the minimum cost path from the top-left corner of a grid to cell (n,m).

The final answer might be:

minimum cost to reach (n,m)

This immediately suggests:

dp[i][j]

where:

dp[i][j] = minimum cost to reach cell (i,j)

Working backward from the final answer frequently reveals the correct state.

Recipe 2: Describe a Subproblem

Every state should correspond to a smaller problem of the same type.

Consider the staircase problem.

Given:

You may climb 1 or 2 steps.
How many ways can you reach step n?

Natural state:

dp[i]

Meaning:

number of ways to reach step i

Now every state asks the same question as the original problem, just for a smaller staircase.

This self-similarity is one of the strongest indicators that the state is correct.

Recipe 3: Store Only Necessary Information

Consider finding the longest increasing subsequence.

A naive attempt might store:

entire sequence used so far

This creates an enormous state space.

Instead we observe that only a few facts affect future decisions.

One useful state is:

dp[i]

meaning:

length of the longest increasing subsequence ending at position i

The actual sequence is unnecessary during optimization.

A useful rule:

Store information required for future decisions, not information that merely explains the past.

Example: Coin Change

Problem:

Given coin values, find the minimum number of coins needed to make amount n.

Final answer:

minimum coins for amount n

Natural state:

dp[x]

where:

dp[x] = minimum number of coins needed to form amount x

The state contains only:

  • current amount

Nothing else influences future decisions.

This produces a compact state space of size:

$$ O(n) $$

Example: Grid Paths

Problem:

Count paths from (0,0) to (n,m).
Move only right or down.

Natural state:

dp[i][j]

Meaning:

number of paths to cell (i,j)

The state captures:

  • row
  • column

No additional information is needed.

State count:

$$ O(nm) $$

Example: Knapsack

Problem:

Select items with maximum value.
Total weight ≤ W.

A common state is:

dp[i][w]

Meaning:

maximum value obtainable
using first i items
with capacity w

Two facts determine future possibilities:

  • current item index
  • remaining capacity

Removing either makes the problem impossible to solve.

Adding extra information usually wastes memory.

State Dimensions

Every state variable creates a dimension.

Examples:

State Dimensions
dp[i] 1
dp[i][j] 2
dp[i][j][k] 3
dp[i][j][k][m] 4

Complexity grows rapidly.

If:

n = 100

Then:

Dimensions States
1 100
2 10,000
3 1,000,000
4 100,000,000

This phenomenon is called the curse of dimensionality.

One of the most important optimization techniques in dynamic programming is reducing the number of state dimensions.

Example: Longest Common Subsequence

Given strings:

A
B

Natural state:

dp[i][j]

Meaning:

length of the LCS
between A[0...i]
and B[0...j]

Each dimension corresponds to progress within one string.

The state space size becomes:

$$ O(nm) $$

which is manageable.

Attempting to store the actual subsequence would dramatically increase complexity.

State Compression Thinking

Suppose a state is:

dp[i][j][k]

Ask:

Can one variable be derived from the others?

If yes:

remove it

Example:

current row
current column
steps taken

Sometimes:

$$ steps = row + column $$

The third dimension becomes redundant.

The state shrinks from:

$$ O(n^3) $$

to:

$$ O(n^2) $$

A small observation can produce enormous performance gains.

Common State Patterns

Prefix States

dp[i]

Represents:

solution using first i elements

Examples:

  • Fibonacci
  • Staircase
  • Coin change

Position States

dp[i][j]

Represents:

state at coordinate (i,j)

Examples:

  • Grid paths
  • Shortest path in matrix

Interval States

dp[l][r]

Represents:

solution inside interval [l,r]

Examples:

  • Matrix chain multiplication
  • Interval merging

Subset States

dp[mask]

Represents:

solution for a subset

Examples:

  • Traveling salesman
  • Set cover variants

Tree States

dp[node]

Represents:

solution for subtree rooted at node

Examples:

  • Tree diameter
  • Maximum independent set on trees

State Design Checklist

Before implementing a recurrence, verify:

  1. Does the state describe a well-defined subproblem?

  2. Can every state be computed from smaller states?

  3. Does the state contain only necessary information?

  4. Can any dimension be removed?

  5. Is the total number of states manageable?

  6. Can the final answer be extracted directly?

If any answer is "no", redesign the state before proceeding.

Complexity Analysis

If a state has:

$$ S $$

possible values and each transition costs:

$$ T $$

then total running time is approximately:

$$ O(S \times T) $$

Therefore state count often dominates overall complexity.

Many successful dynamic programming optimizations come from reducing:

$$ S $$

rather than improving transitions.

Takeaway

Dynamic programming begins with state design, not recurrence design. A state should represent a clear subproblem, contain only information required for future decisions, minimize dimensions whenever possible, and make the final answer easy to extract. Once a good state exists, recurrences, correctness proofs, and optimizations become significantly easier to derive.