Dynamic Programming
Introduction
Dynamic Programming (DP) is a fundamental algorithm design paradigm that solves complex problems by breaking them down into simpler overlapping subproblems and storing their solutions to avoid redundant computation. The key insight is that many problems have overlapping subproblems that are solved multiple times, and DP exploits this overlap by computing each subproblem only once and storing the result.
Core Principles:
Define Subproblems: Identify how to break the problem into smaller, related subproblems
Store Solutions: Use a table to store solutions to subproblems (no recursion or memoization in pure DP)
Build Bottom-Up: Solve smaller subproblems first, using their solutions to build up to the final answer
Extract Result: Retrieve the final answer from the computed table
Key Characteristics
Optimal Substructure
The optimal solution to the problem can be constructed from optimal solutions of its subproblems
This property is necessary but not sufficient for dynamic programming
Enables building solutions incrementally from smaller subproblems
Overlapping Subproblems
The same subproblems are solved multiple times during naive recursive computation
DP eliminates this redundancy by storing solutions to subproblems
Each unique subproblem is computed exactly once and reused as needed
Bottom-Up Construction
Solutions are built iteratively from smaller subproblems to larger ones
Table entries are filled in a specific order that ensures dependencies are satisfied
No recursion is used in the implementation (distinguishes DP from memoization)
Essential Components of a DP Solution
A complete dynamic programming solution consists of three required sections. These components form the standard format for presenting and evaluating DP solutions in academic and professional contexts.
(a) Subproblem Definition
The subproblem definition articulates what each table entry represents in plain language.
Requirements:
Always build on a subset of the input (e.g.,
A[1..i])Conceptually defined against "previous" subproblems, not the entire input
Expressed using words: "T(i,j) is the ..."
Should use terminology from the problem statement without redefining meanings
May or may not require inclusion of the last element
Inclusion Constraint:
Phrases like "which includes A[i]", "ending at A[i]", or "which includes i" mean element
A[i]must be used in the subproblem outputThis is distinct from the range of indices
[1..i]considered for the subproblem
(b) Recurrence Relation
The recurrence relation provides a mathematical definition of how table entries relate to each other.
Requirements:
Mathematical notation only - no programming constructs, no pseudocode
Single mathematical definition - the table gets one definition for all entries
Recursive to smaller subproblems - each entry defined using previously computed entries
Well-defined references - any referenced entry must have a definition
No self-reference - cannot define
T(i)usingT(i)itselfInclude base cases - with their applicable bounds clearly specified
Provide bounds for all variables - scoped to where they are used
Focus on table definitions - not on the final return value
Important Notes:
Bounds convey directionality of table fill
0 ≤ i ≤ nsuggests left-to-right fill ordern ≥ i ≥ 0suggests right-to-left fill order (for suffix-based approaches)
Each table entry can be defined only once (no contradictions)
All referenced entries must be well-defined before use
Helper indices can be added to access ranges of previous entries
Common Pitfalls to Avoid:
❌ Self-referential definition:
✅ Correct definition:
❌ Pseudocode constructs:
✅ Conditional mathematical notation:
❌ Overlapping base cases and recurrence:
✅ Mutually exclusive bounds:
(c) Implementation Analysis
This section analyzes the computational complexity and efficiency of the dynamic programming solution based on the recurrence relation.
(c.1) Number of Subproblems
State the total number of distinct subproblems (table entries) in Big-O notation.
Examples:
One-dimensional table from
-1ton: O(n)Two-dimensional table
T(i,j)where1 ≤ i ≤ n, 1 ≤ j ≤ m: O(n·m)Three-dimensional table: O(n·m·k)
(c.2) Runtime to Fill the Table
State the worst-case time complexity to compute all table entries in Big-O notation.
Calculation approach:
Identify the cost to compute one table entry
Multiply by the number of table entries
Simplify to Big-O notation
(c.3) Extracting the Final Answer
Describe how and where the final result is extracted from the completed table.
Common patterns:
T(n)- last entryT(n,m)- bottom-right entrymax{T(n,*)}ormax{T(*,*)}- maximum value in row/tablesum{T(*,*)}- sum of entries
(c.4) Runtime to Extract the Answer
State the time complexity to extract and return the final answer in Big-O notation.
Problem-Solving Workflow
Step 1: Identify the Problem Pattern
Sequence problems: LIS, LCS, Maximum Subarray
Optimization problems: Knapsack, Matrix Chain Multiplication
Counting problems: Number of ways to reach a state
Interval problems: Breaking strings/arrays into optimal intervals
Step 2: Define the Subproblem
Identify what each table entry represents in plain language
Build on prefixes/suffixes of the input (e.g.,
A[1..i])Determine if the last element must be included
Use terminology from the problem statement
Step 3: Derive the Recurrence Relation
Determine base case(s) - smallest directly solvable subproblems
Express
T(i)orT(i,j)in terms of smaller subproblemsHandle all conditional cases
Specify bounds for all variables
Ensure no table entry is defined multiple times
Step 4: Analyze Complexity
Count the number of subproblems
Determine cost per entry
Calculate total fill time
Identify extraction method and cost
Step 5: Implement (If Required)
Initialize table with appropriate dimensions
Fill base cases
Iterate in correct order (respecting dependencies)
Apply recurrence relation
Extract final answer
Common DP Patterns
Linear Sequence DP
One-dimensional table
T(i)based on prefixA[1..i]Examples: Fibonacci, Maximum Subarray, Longest Increasing Subsequence
Two Sequences DP
Two-dimensional table
T(i,j)based on prefixes of two inputsExamples: Longest Common Subsequence, Edit Distance
Bounded Knapsack DP
Table
T(i,w)tracking items and capacity constraintsExamples: 0/1 Knapsack, Subset Sum
Interval DP
Table
T(i,j)representing solutions over intervals[i,j]Examples: Matrix Chain Multiplication, Palindrome Partitioning
Common Pitfalls to Avoid
Starting with code - Always define subproblems and recurrence mathematically first
Self-referential definitions -
T(i)cannot appear on both sides of the equationPseudocode in recurrence - No
forloops,ifstatements as control flowMultiple table assignments - Each entry can only be defined once
Undefined references - Ensure all referenced entries (like
T(i-2)) have definitionsMissing or overlapping bounds - Base cases and recurrence must have mutually exclusive ranges
Incomplete variable bounds - All variables must have specified ranges
Notation Conventions
Table indexing:
T[i]orT(i)(be consistent)Conditions:
if,otherwise,s.t.(such that),whereLogical operators:
and,or,||,&&,!Special values:
∞(infinity) for impossible statesEmpty set:
{}or∅Base cases: Must be non-recursive with hard-coded values
Multiple tables: Rare but valid; each needs its own definition
Last updated