13.5 One-Dimensional Dynamic Programming
One-dimensional dynamic programming is the simplest and most common form of dynamic programming.
13.5 One-Dimensional Dynamic Programming
One-dimensional dynamic programming is the simplest and most common form of dynamic programming. The state is represented by a single index, and each state typically depends on a small number of earlier states. Many classical problems, including Fibonacci numbers, staircase counting, coin change, rod cutting, and longest increasing subsequence, begin with a one-dimensional formulation.
Mastering one-dimensional dynamic programming develops the core skills needed for all later dynamic programming techniques. State design, recurrence construction, initialization, iteration ordering, and space optimization all appear here in their simplest form.
Problem
How do we design and implement dynamic programming solutions where each state is identified by a single variable?
Understanding Linear State Spaces
A one-dimensional state usually looks like:
$$ dp[i] $$
The index often represents:
- a position
- a prefix length
- an amount
- a time step
- a resource level
Examples:
dp[i] = best solution using first i elements
dp[i] = minimum cost to reach position i
dp[i] = number of ways to form amount i
dp[i] = longest subsequence ending at i
The key characteristic is that every state is described by one coordinate.
Example: Fibonacci Numbers
State:
$$ dp[i] $$
Meaning:
ith Fibonacci number
Recurrence:
$$ dp[i] = dp[i-1] + dp[i-2] $$
Initialization:
$$ dp[0]=0 $$
$$ dp[1]=1 $$
Implementation:
func fib(n int) int {
if n <= 1 {
return n
}
dp := make([]int, n+1)
dp[0] = 0
dp[1] = 1
for i := 2; i <= n; i++ {
dp[i] = dp[i-1] + dp[i-2]
}
return dp[n]
}
This is the canonical one-dimensional DP.
Example: Staircase Counting
Problem:
You may climb 1 or 2 steps.
Count ways to reach step n.
State:
$$ dp[i] $$
Meaning:
number of ways to reach step i
Recurrence:
$$ dp[i] = dp[i-1] + dp[i-2] $$
Initialization:
$$ dp[0]=1 $$
$$ dp[1]=1 $$
The interpretation differs from Fibonacci, but the recurrence is identical.
This illustrates an important principle:
Different problems can share the same dynamic programming structure.
Example: Minimum Cost Climbing Stairs
Problem:
Each stair has a cost.
Minimize total cost.
State:
$$ dp[i] $$
Meaning:
minimum cost to reach stair i
Recurrence:
$$ dp[i] = cost[i] + \min(dp[i-1],dp[i-2]) $$
Implementation:
for i := 2; i < n; i++ {
dp[i] =
cost[i] +
min(dp[i-1], dp[i-2])
}
This introduces minimization rather than counting.
Example: Coin Change
Problem:
Minimum number of coins
needed to form amount x
State:
$$ dp[x] $$
Meaning:
minimum coins for amount x
Recurrence:
$$ dp[x] = 1+ \min(dp[x-c]) $$
for all valid coin values (c).
Implementation:
for amount := 1; amount <= target; amount++ {
for _, coin := range coins {
if amount >= coin {
dp[amount] = min(
dp[amount],
dp[amount-coin]+1,
)
}
}
}
Unlike Fibonacci, each state may depend on many earlier states.
Example: Rod Cutting
Problem:
Cut rod of length n
for maximum profit
State:
$$ dp[i] $$
Meaning:
maximum revenue
for rod length i
For every possible first cut:
$$ dp[i] = \max ( price[j] + dp[i-j] ) $$
for:
$$ 1 \le j \le i $$
Implementation:
for length := 1; length <= n; length++ {
for cut := 1; cut <= length; cut++ {
dp[length] = max(
dp[length],
price[cut]+dp[length-cut],
)
}
}
This pattern appears frequently in optimization problems.
State Dependency Patterns
Most one-dimensional DPs fall into several common categories.
Fixed-Width Dependencies
Example:
$$ dp[i] = dp[i-1] + dp[i-2] $$
Dependencies are limited to a constant number of earlier states.
Examples:
- Fibonacci
- Staircase counting
Complexity:
$$ O(n) $$
Variable-Width Dependencies
Example:
$$ dp[i] = \min(dp[i-j]) $$
for many values of (j).
Examples:
- Coin change
- Word segmentation
- Rod cutting
Complexity:
$$ O(n^2) $$
or
$$ O(nk) $$
depending on transition count.
Prefix Dynamic Programming
State:
$$ dp[i] $$
Meaning:
solution using first i elements
Examples:
- Sequence optimization
- String decomposition
- Resource allocation
The state grows from left to right.
Position Dynamic Programming
State:
$$ dp[i] $$
Meaning:
best solution ending at position i
Examples:
- Longest increasing subsequence
- Maximum subarray
- Path optimization
Each state focuses on a location rather than a prefix.
Example: Maximum Subarray Sum
Given:
array a
State:
$$ dp[i] $$
Meaning:
maximum subarray sum
ending at position i
Recurrence:
$$ dp[i] = \max ( a[i], dp[i-1]+a[i] ) $$
The choice:
- start new subarray
- extend previous subarray
Implementation:
dp[0] = a[0]
for i := 1; i < n; i++ {
dp[i] = max(
a[i],
dp[i-1]+a[i],
)
}
This is commonly known as Kadane's algorithm.
Example: Longest Increasing Subsequence
State:
$$ dp[i] $$
Meaning:
length of LIS ending at i
Recurrence:
$$ dp[i] = 1+ \max(dp[j]) $$
for:
$$ j<i $$
and
$$ a[j]<a[i] $$
Implementation:
for i := 0; i < n; i++ {
dp[i] = 1
for j := 0; j < i; j++ {
if a[j] < a[i] {
dp[i] = max(
dp[i],
dp[j]+1,
)
}
}
}
This demonstrates a common (O(n^2)) transition pattern.
Space Optimization
Many one-dimensional DPs require only a few previous states.
Consider:
$$ dp[i] = dp[i-1] + dp[i-2] $$
Full table:
$$ O(n) $$
memory.
Optimized:
prev2 := 0
prev1 := 1
for i := 2; i <= n; i++ {
current := prev1 + prev2
prev2 = prev1
prev1 = current
}
Memory becomes:
$$ O(1) $$
This optimization is possible whenever old states are never revisited.
Common Mistakes
Incorrect Initialization
Many recurrences rely heavily on base states.
Example:
dp[0]
often represents:
- empty sequence
- zero amount
- starting position
Misinterpreting this state breaks the entire solution.
Wrong Iteration Direction
Suppose:
$$ dp[i] = dp[i+1] +1 $$
Then left-to-right iteration fails.
Dependencies must already exist before computation.
Mixing Meanings
Example:
dp[i]
sometimes interpreted as:
first i elements
and elsewhere as:
ending at position i
A state must maintain one consistent meaning.
Overflow
Counting problems often grow rapidly.
Use appropriate numeric types when state values become large.
Recognizing One-Dimensional DP
Several clues strongly suggest a one-dimensional formulation.
Single Variable Progress
Examples:
- amount
- position
- length
- time
Smaller Prefixes
Problems involving:
first i elements
often lead naturally to:
$$ dp[i] $$
Sequential Decisions
When decisions occur in a linear order, a one-dimensional state frequently suffices.
Complexity Analysis
Let:
$$ n $$
be the number of states.
If each state examines:
$$ k $$
transitions:
$$ O(nk) $$
If:
$$ k=O(1) $$
then:
$$ O(n) $$
running time.
Memory:
$$ O(n) $$
before optimization.
Many one-dimensional DPs can be reduced to:
$$ O(1) $$
or
$$ O(k) $$
memory.
Takeaway
One-dimensional dynamic programming organizes subproblems along a single axis such as position, amount, length, or time. Despite its apparent simplicity, this model captures a wide variety of important problems, including optimization, counting, feasibility, and sequence analysis. Understanding how to design states, derive transitions, initialize base cases, and optimize memory in one-dimensional dynamic programming provides the foundation for all higher-dimensional dynamic programming techniques that follow.