Table of Contents
921 words
5 minutes
Dynamic Programming: TypeScript in Practice from Climbing Stairs to the Knapsack Problem

Overview#

Dynamic Programming (DP) avoids repeated computation by storing the results of subproblems. It is suitable for scenarios with optimal substructure and overlapping subproblems, such as path counting, sequence matching, knapsack selection, and interval optimal values.

Prerequisites

  • Recursion / Memoized Search: Understanding state dependencies from a top-down perspective
  • Arrays and Matrices: One-dimensional and two-dimensional DP tables are the most common state storage
  • State Transitions: Deriving the current problem from known smaller problems

Problem Definition#

Given an optimization or counting problem that can be decomposed into overlapping subproblems, define the state and solve for the final answer through a transition equation.

ElementDescription
InputSequence, capacity, target value, or two strings
OutputMaximum/minimum value, number of solutions, longest length, or minimum number of operations
Core Statedp[i], dp[i][j], or a compressed one-dimensional array
Key QuestionsState meaning, transition equation, initialization, iteration order

Core Principle: Step-by-Step Illustration#

Taking stair climbing as an example, reaching step i can only come from i - 1 or i - 2:

dp0=1 dp2 dp1=1 dp3 dp4

The state transition is:

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

The difficulty of dynamic programming usually lies not in the code, but in reformulating the problem as “how the current state can be obtained from smaller states.”


Detailed Algorithm Steps#

Algorithm: SolveDP(problem)
Input: A decomposable problem
Output: Optimal value, number of solutions, or feasibility
1. Define the state: what dp[i] or dp[i][j] represents
2. Write the transition equation: which previous states the current state comes from
3. Initialize boundary states: empty array, first row, first column, etc.
4. Determine the traversal order: ensure dependent states have already been computed
5. Return the answer: dp[n], dp[m][n], or the maximum value in the table

Complexity Analysis:

ProblemState DefinitionTime ComplexitySpace Complexity
Stair Climbingdp[i] number of ways to reach step iO(n)O(1)
House Robberdp[i] maximum profit from first i housesO(n)O(1)
LCSdp[i][j] longest common subsequence of two prefixesO(mn)O(mn)
Edit Distancedp[i][j] minimum operations to convert prefixesO(mn)O(mn)
0-1 Knapsackdp[w] maximum value at capacity wO(nW)O(W)

TypeScript Implementation#

1. Stair Climbing#

function climbStairs(n: number): number {
if (n <= 2) return n;
let prev2 = 1;
let prev1 = 2;
for (let i = 3; i <= n; i++) {
const current = prev1 + prev2;
prev2 = prev1;
prev1 = current;
}
return prev1;
}

2. House Robber#

function rob(nums: number[]): number {
let prev2 = 0;
let prev1 = 0;
for (const money of nums) {
const current = Math.max(prev1, prev2 + money);
prev2 = prev1;
prev1 = current;
}
return prev1;
}

3. Longest Common Subsequence#

function longestCommonSubsequence(text1: string, text2: string): number {
const m = text1.length;
const n = text2.length;
const dp = Array.from({ length: m + 1 }, () => new 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];
}

4. Edit Distance#

function minDistance(word1: string, word2: string): number {
const m = word1.length;
const n = word2.length;
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
for (let i = 0; i <= m; i++) dp[i][0] = i;
for (let j = 0; j <= n; j++) dp[0][j] = j;
for (let i = 1; i <= m; i++) {
for (let j = 1; j <= n; j++) {
if (word1[i - 1] === word2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
}
}
}
return dp[m][n];
}

5. 0-1 Knapsack#

function knapsack(weights: number[], values: number[], capacity: number): number {
const dp = new Array(capacity + 1).fill(0);
for (let i = 0; i < weights.length; i++) {
for (let w = capacity; w >= weights[i]; w--) {
dp[w] = Math.max(dp[w], dp[w - weights[i]] + values[i]);
}
}
return dp[capacity];
}

6. Longest Increasing Subsequence#

function lengthOfLIS(nums: number[]): number {
const tails: number[] = [];
for (const num of nums) {
let left = 0;
let right = tails.length;
while (left < right) {
const mid = left + Math.floor((right - left) / 2);
if (tails[mid] < num) left = mid + 1;
else right = mid;
}
tails[left] = num;
}
return tails.length;
}

Engineering Optimization: State Compression and Iteration Order#

OptimizationApproachNotes
One-dimensional compressionKeep only the previous row or necessary statesMust confirm dependency direction
Rolling variablesE.g., stair climbing keeps only two valuesSuitable when depending on a fixed number of old states
0-1 KnapsackIterate capacity in reverse orderPrevents the same item from being used repeatedly
Unbounded KnapsackIterate capacity in forward orderAllows the same item to be used repeatedly
Memoized SearchCache results top-downCloser to the recursive definition, intuitive for debugging

DP optimization cannot focus solely on space reduction; it must also ensure that state dependencies are not overwritten prematurely.


Applications and Limitations#

Typical Applications#

  • Path counting, stair climbing, grid paths
  • Sequence problems: LCS, LIS, edit distance
  • Knapsack and resource allocation
  • Stock trading, interval DP, bitmask DP

Limitations#

LimitationDescription
Difficult state designUnclear state meaning leads to incorrect transitions
Space can be largeTwo-dimensional or multi-dimensional DP easily consumes large amounts of memory
Not suitable without overlapping subproblemsDP yields limited benefit when there is no repeated computation

Summary#

Define states Derive transitions Initialize boundaries Determine iteration order Compute the answer Compress states if needed

Key Takeaways:

  1. DP solves problems with overlapping subproblems and optimal substructure.
  2. Before writing DP, first clarify the meaning of each position in the dp array.
  3. The iteration order must ensure that dependency states have already been computed.
  4. The core of state compression is ensuring that old states are not incorrectly overwritten.
Dynamic Programming: TypeScript in Practice from Climbing Stairs to the Knapsack Problem
https://www.hehonglei.com/technology/dp/
Author
HHLBlog
Published at
2026-04-26
License
CC BY-NC-SA 4.0