Dynamic Programming Essentials: Understanding Key Concepts and Exploring DP Variants

๐ง What is Dynamic Programming?
Dynamic Programming (DP) is a powerful algorithmic technique that merges the correctness of complete search with the efficiency of greedy algorithms. It is especially useful when a problem can be broken down into overlapping subproblems that can be solved independently and combined for the final solution.
In essence, dynamic programming avoids redundant work by storing solutions to subproblems and reusing them when neededโmaking it significantly faster than naive recursion or brute-force approaches.
๐ When to Use Dynamic Programming
Dynamic programming is applicable when:
The problem has overlapping subproblems (the same subproblem occurs multiple times).
The problem exhibits optimal substructure (the optimal solution can be constructed from optimal solutions to its subproblems).
Dynamic Programming Approaches: Top-Down vs Bottom-Up
๐ง 1. Top-Down Approach (Memoization)
The Top-Down approach, also known as memoization, starts with the original problem and recursively breaks it down into smaller subproblems. It stores the result of each subproblem to avoid redundant calculations.
๐ How It Works:
You write a recursive function to solve the problem.
Before computing a result, check if the result is already in a memoization table.
If it is, return it.
If not, compute it and store the result in the memo table.
โ Pros:
Easy to implement if you already have a recursive solution.
Avoids unnecessary computation by only solving subproblems that are actually needed.
๐งฑ 2. Bottom-Up Approach (Tabulation)
The Bottom-Up approach, or tabulation, builds up the solution iteratively from the smallest subproblems. You fill a dp
table starting from the base cases.
๐ How It Works:
Define a
dp[]
array (or table).Initialize base case(s), e.g.,
dp[0]
,dp[1]
, etc.Iterate from smallest to largest subproblems and build the solution using the recurrence.
โ Pros:
No recursion overhead, so usually faster and stack-safe.
Good for problems where all subproblems are needed.
๐งช Example: Fibonacci Numbers and Dynamic Programming
Letโs understand the power of dynamic programming with the Fibonacci sequence.
๐ข Fibonacci Sequence:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, ...
The n
th Fibonacci number is defined as: fib(n)=fib(nโ1)+fib(nโ2)
With base cases:
fib(0) = 0
fib(1) = 1
โ Brute Force (Recursive)
Hereโs the naive recursive approach:
// C++ program to find Fibonacci number using recursion.
#include <bits/stdc++.h>
using namespace std;
// Function to find nth fibonacci number
int fib(int n) {
if (n <= 1) return n;
return fib(n - 1) + fib(n - 2);
}
int main() {
int n = 4;
cout << fib(n);// output: 3
return 0;
}
โฑ Time Complexity: O(2n)
Because each function calls two others โ exponential growth.
Below is the recursion tree of the above recursive solution.
How will Dynamic Programming (DP) Work?
Letโs us now see the above recursion tree with overlapping subproblems highlighted with same color. We can clearly see that that recursive solution is doing a lot work again and again which is causing the time complexity to be exponential.
1๏ธโฃ Identify Subproblems
Divide the main problem into smaller, manageable subproblems.
For example, to compute F(n)
in the Fibonacci sequence, you need the results of F(n-1)
and F(n-2)
. These are your subproblems.
2๏ธโฃ Store Solutions (Memoization)
Solve each subproblem once and save the result in a table (array or map).
This way, if you ever need F(3)
again, you can just look it up instead of recalculating it from scratch.
3๏ธโฃ Build Up the Solution
Use the stored results to compute the final answer.
For example, once you have F(n-1)
and F(n-2)
stored, you can compute F(n) = F(n-1) + F(n-2)
efficiently.
4๏ธโฃ Avoid Recomputations
Dynamic Programming ensures each subproblem is solved only once.
This dramatically reduces time complexityโfrom exponential to linear in the case of Fibonacci.
โ Mathematical Recurrence:
$$\text{fib}(n) = \begin{cases} 0 & \text{if } n = 0 \\ 1 & \text{if } n = 1 \\ \text{fib}(n - 1) + \text{fib}(n - 2) & \text{if } n > 1 \end{cases}$$
Using Memoization Approach - O(n) Time and O(n) Space
#include <iostream>
#include <vector>
using namespace std;
int solve(int n, vector<int> &memo) {
// Base case
if (n <= 1) {
return n;
}
// To check if output already exists
if (memo[n] != -1) {
return memo[n];
}
// Calculate and save output for future use
memo[n] = solve(n - 1, memo) + solve(n - 2, memo);
return memo[n];
}
int fib(int n) {
vector<int> memo(n + 1, -1);// we can use vector or map
return solve(n, memo);
}
int main() {
int n = 4;
cout << fib(n);
return 0;
}
Using Tabulation Approach - O(n) Time and O(n) Space
#include <iostream>
#include <vector>
using namespace std;
// Function for calculating the nth Fibonacci number
int fibo(int n) {
vector<int> dp(n + 1);
// Storing the independent values in dp
dp[0] = 0;
dp[1] = 1;
// Using the bottom-up approach
for (int i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
int main() {
int n = 4;
cout << fibo(n);
return 0;
}
Here is a structured guide to master every major Dynamic Programming (DP) category, with 5 carefully selected LeetCode problems per section. These problems are handpicked to help you gradually build intuition and skill in each DP type.
๐งฑ 1. Linear DP (1D DP)
This is the most basic form of DP. We define dp[i]
as the optimal answer for the first i
elements.
๐งช Core Idea
Build dp[i]
using previous states like dp[i-1]
, dp[i-2]
, etc.
Climbing Stairs โ Ways to reach the
n
th step with 1 or 2 steps at a time.My Solution: View on GitHub
Fibonacci Numbers โ Standard DP recurrence.
Min Cost Climbing Stairs โ Pay a cost to step; find the minimum total cost.
House Robber โ Max sum without robbing two adjacent houses.
Decode Ways โ Count how many ways to decode a numeric string (like
'12' โ AB or L
).
๐ฒ 2. 2-Dimensional DP (Table DP)
Used when the state depends on two indices (e.g., ranges, substrings, etc.)
Longest Common Subsequence (LCS) โ Between two strings.
Edit Distance โ Minimum operations to convert one string into another.
Interleaving Strings โ Check if a string is formed by interleaving two others.
Distinct Subsequences โ Count subsequences of one string that form another.
Wildcard Matching โ Pattern matching with and
?
.
๐งฑ 3. DP on Grid
This is a special form of 2D DP, focused on moving in a grid (usually right and down).
Unique Paths โ Count number of paths from top-left to bottom-right.
Minimum Path Sum โ Find path with the lowest cost.
Coin Change on Grid โ Paths with certain weights or restrictions.
Grid with Obstacles โ Same as Unique Paths but some blocks are disabled.
Cherry Pickup โ Advanced grid DP with two people collecting cherries.
๐ 4. Knapsack DP
Used when we choose items under constraints (weight/volume/cost).
0/1 Knapsack โ Pick items with max value without exceeding weight.
Subset Sum โ Can we make a given sum using a subset?
Partition Equal Subset Sum โ Can we split an array into two equal-sum parts?
Target Sum โ Assign + or - to reach a target.
Count of Subsets with Given Sum โ Count how many subsets reach a given sum.
๐ 5. Longest Increasing Subsequence (LIS)
LIS (O(n^2) and O(n log n)) โ Find length of the longest increasing subsequence.
Maximum Sum Increasing Subsequence โ Max sum instead of length.
Number of LIS โ Count all increasing subsequences of maximum length.
LIS on Circular Array โ Wrap-around LIS.
Russian Doll Envelopes โ 2D LIS variation.
โ๏ธ 6. Longest Common Subsequence (LCS)
LCS (Basic) โ Longest common subsequence of two strings.
Shortest Common Supersequence โ Shortest string that has both strings as subsequences.
Minimum Insertion/Deletion to Make Equal โ Using LCS length.
Print the LCS โ Reconstruct the LCS, not just length.
Longest Palindromic Subsequence โ LCS of
s
andreverse(s)
.
๐ค 7. DP on String
Palindrome Partitioning II โ Minimum cuts for palindrome substrings.
Regular Expression Matching โ Pattern matching with
.
and .Scramble String โ Recursive check using DP and memoization.
Word Break โ Check if a string can be split into valid dictionary words.
Longest Palindromic Substring โ Find the longest substring that is a palindrome.
โ 8. Cumulative Sum (Prefix Sum + DP)
Range Sum Query โ Fast sum queries in a range.
Minimum Cost to Merge Stones โ DP with prefix sums.
Count Subarrays with Sum K โ Using prefix sums and hashing.
Maximum Sum Rectangle in 2D Matrix โ Prefix row sums + Kadaneโs.
Subarray Sum Equals K โ Prefix + hashmap.
๐งฎ 9. Matrix Chain Multiplication (Interval DP)
Matrix Chain Multiplication โ Parenthesize matrix multiplications to minimize cost.
Burst Balloons โ Choose order to burst balloons to maximize coins.
Minimize Cost to Cut a Stick โ Optimal way to cut a stick into parts.
Palindrome Partitioning โ Min cuts to make all substrings palindromes.
Boolean Parenthesization โ Count ways to parenthesize boolean expressions to be true.
โก 10. Kadane's Algorithm (Subarray Optimization)
Maximum Subarray (Kadane) โ Classic problem (LeetCode #53).
Maximum Product Subarray โ Keep track of min and max.
Max Sum Submatrix (2D Kadane) โ Extend Kadane to 2D matrix.
Best Time to Buy and Sell Stock โ DP with max profit difference.
Maximum Sum Circular Subarray โ Use Kadane twice (normal and inverted).
Subscribe to my newsletter
Read articles from Ganaa OyunDalai directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
