Competitive Coding
Unit 5: Dynamic Programming Problems
Master the seven classic DP problems โ from Binomial Coefficients to Balanced Partitions โ with complete recurrences, table traces, C++/Python code, and optimization techniques used at top tech companies.
โฑ๏ธ 10 hrs theory + 8 hrs practice | ๐ฐ Earning Potential: โน15Kโโน50K/month | ๐ 30 MCQs (Bloom's Mapped)
๐ผ Jobs this unlocks: SDE at FAANG (โน25โ60 LPA) | Competitive Programmer (โน15โ40 LPA) | Algorithm Engineer (โน20โ50 LPA)
Opening Hook โ DP Powers the Products You Use Daily
๐ฆ How Flipkart's Warehouse Optimizer Packs Maximum Items
Every time you order from Flipkart during the Big Billion Days sale, a warehouse robot needs to decide how to stack boxes of different sizes into delivery trucks to maximise the number of items shipped per trip. This isn't trial-and-error โ it's the Box Stacking Problem, a classic Dynamic Programming challenge. Flipkart's logistics engine uses a variant of DP-based stacking to reduce shipping costs by 18% โ saving โน200+ crores annually.
Meanwhile, when Netflix recommends your next binge-watch, it computes the Longest Common Subsequence (LCS) between your watch history and millions of other users. If you watched Breaking Bad โ Narcos โ Money Heist, and another user watched Breaking Bad โ Peaky Blinders โ Narcos โ Money Heist โ Ozark, the LCS is [Breaking Bad, Narcos, Money Heist] โ and Netflix recommends Peaky Blinders and Ozark to you. That's DP in production at scale.
What if YOU could build these algorithms? In this unit, you'll master 7 classic DP problems that appear in 60%+ of FAANG interviews. These same algorithms power spell-checkers (Edit Distance), DNA analysis (LCS), investment portfolios (Knapsack), and compiler optimizers (LIS).
Learning Outcomes โ Bloom's Taxonomy Mapped (12 Outcomes)
| Bloom's Level | Learning Outcome |
|---|---|
| ๐ต Remember | LO1: State the recurrence relations for Binomial Coefficient, 0/1 Knapsack, LIS, LCS, and Edit Distance |
| ๐ต Remember | LO2: List the base cases and boundary conditions for each of the 7 DP problems |
| ๐ข Understand | LO3: Explain the principle of optimal substructure and overlapping subproblems using the Knapsack problem as an example |
| ๐ข Understand | LO4: Describe how the Box Stacking problem reduces to a variant of Longest Increasing Subsequence |
| ๐ก Apply | LO5: Trace and fill complete DP tables for Edit Distance, LCS, and Knapsack given specific inputs |
| ๐ก Apply | LO6: Implement all 7 DP problems in C++ and Python with correct time and space complexity |
| ๐ Analyze | LO7: Compare the O(nยฒ) and O(n log n) approaches for LIS and determine when each is appropriate |
| ๐ Analyze | LO8: Analyze how space optimization reduces 2D DP tables to 1D arrays in Knapsack and Binomial Coefficient |
| ๐ด Evaluate | LO9: Evaluate whether a given problem has DP structure (optimal substructure + overlapping subproblems) vs. greedy structure |
| ๐ด Evaluate | LO10: Assess the trade-offs between top-down memoization and bottom-up tabulation approaches |
| ๐ฃ Create | LO11: Design a DP solution for a novel problem by identifying states, transitions, and base cases |
| ๐ฃ Create | LO12: Construct backtracking logic to reconstruct the actual solution (not just optimal value) for LCS and Edit Distance |
Concept Explanation โ 7 Classic DP Problems
Each problem follows the standard DP methodology: Problem Statement โ Intuition โ Recurrence โ DP Table Trace โ Code (C++/Python) โ Complexity โ Optimization. Master this framework and you can solve any new DP problem.
1. Binomial Coefficient C(n, k)
๐ Problem Statement
Compute the Binomial Coefficient C(n, k) = number of ways to choose k items from n items. Also written as โฟCโ or "n choose k".
Example: C(5, 2) = 10 (there are 10 ways to choose 2 items from 5).
Intuition โ Pascal's Triangle
Every entry in Pascal's triangle is the sum of the two entries directly above it. The value at row n, position k is exactly C(n, k). Instead of computing factorials (which overflow for large n), we build the triangle row by row.
Recurrence Relation
Recurrence // Pascal's Rule: C(n, k) = C(n-1, k-1) + C(n-1, k) // Base cases: C(n, 0) = 1 // choosing 0 items = 1 way (empty set) C(n, n) = 1 // choosing all items = 1 way
DP Table Trace โ C(5, 2)
| n\k | 0 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|
| 0 | 1 | |||||
| 1 | 1 | 1 | ||||
| 2 | 1 | 2 | 1 | |||
| 3 | 1 | 3 | 3 | 1 | ||
| 4 | 1 | 4 | 6 | 4 | 1 | |
| 5 | 1 | 5 | 10 | 10 | 5 | 1 |
Answer: C(5, 2) = 10 โ
Code โ C++
C++ int binomialCoeff(int n, int k) { vector<vector<int>> dp(n + 1, vector<int>(k + 1, 0)); for (int i = 0; i <= n; i++) { dp[i][0] = 1; // C(i, 0) = 1 for (int j = 1; j <= min(i, k); j++) { if (j == i) dp[i][j] = 1; // C(i, i) = 1 else dp[i][j] = dp[i-1][j-1] + dp[i-1][j]; } } return dp[n][k]; }
Code โ Python
Python def binomial_coeff(n, k): dp = [[0] * (k + 1) for _ in range(n + 1)] for i in range(n + 1): dp[i][0] = 1 for j in range(1, min(i, k) + 1): if j == i: dp[i][j] = 1 else: dp[i][j] = dp[i-1][j-1] + dp[i-1][j] return dp[n][k] print(binomial_coeff(5, 2)) # Output: 10
Complexity
| Metric | Value |
|---|---|
| Time Complexity | O(n ร k) |
| Space Complexity (naive) | O(n ร k) |
| Space Optimized | O(k) โ use 1D array, update right to left |
Space Optimization โ O(k)
C++ int binomialOptimized(int n, int k) { vector<int> dp(k + 1, 0); dp[0] = 1; for (int i = 1; i <= n; i++) for (int j = min(i, k); j > 0; j--) // right to left! dp[j] = dp[j] + dp[j - 1]; return dp[k]; }
dp[j-1] would already be overwritten with the current row's value. Going right to left ensures we use the previous row's values. This pattern appears in Knapsack too!
2. Box Stacking Problem
๐ฆ Problem Statement
Given n types of boxes, each with dimensions (height h, width w, depth d). You can rotate boxes so any side faces up. You can use multiple instances of each type. A box can be placed on top of another only if both its base dimensions are strictly smaller. Find the maximum height of the tallest stack.
Example: Boxes = {(4,6,7), (1,2,3), (4,5,6)}. After generating all rotations and sorting, find max stack height.
Intuition โ Reduce to LIS Variant
For each box, generate all 3 rotations (each face as base). Sort all rotations by base area (decreasing). Now the problem becomes: find the longest increasing subsequence of heights where each box's base dimensions are strictly smaller than the one below it โ a variant of LIS!
Algorithm Steps
- Generate rotations: For box (h, w, d), create 3 rotations where each dimension is the height and the base is (max of remaining, min of remaining)
- Sort by base area in decreasing order
- Apply LIS-like DP: dp[i] = max stack height with box i on top
Recurrence
Recurrence // For each box i (sorted by base area descending): dp[i] = height[i] // base case: just box i alone // For all j < i where box j's base fits strictly inside box i's base: dp[i] = max(dp[i], dp[j] + height[i]) where width[j] < width[i] AND depth[j] < depth[i] Answer = max(dp[0], dp[1], ..., dp[n-1])
Trace Example
Given boxes: (4,6,7), (1,2,3), (4,5,6)
All rotations (h, w, d where w โฅ d):
| # | h | w | d | Base Area |
|---|---|---|---|---|
| 0 | 4 | 7 | 6 | 42 |
| 1 | 6 | 7 | 4 | 28 |
| 2 | 7 | 6 | 4 | 24 |
| 3 | 4 | 6 | 5 | 30 |
| 4 | 5 | 6 | 4 | 24 |
| 5 | 6 | 5 | 4 | 20 |
| 6 | 1 | 3 | 2 | 6 |
| 7 | 2 | 3 | 1 | 3 |
| 8 | 3 | 2 | 1 | 2 |
After sorting by base area (descending) and running DP: Maximum height = 22
Code โ C++
C++ #include <bits/stdc++.h> using namespace std; struct Box { int h, w, d; }; bool cmp(Box &a, Box &b) { return (a.w * a.d) > (b.w * b.d); } int maxStackHeight(int height[], int width[], int depth[], int n) { vector<Box> rot; for (int i = 0; i < n; i++) { int dims[] = {height[i], width[i], depth[i]}; sort(dims, dims+3); // 3 rotations: each dimension as height rot.push_back({dims[0], max(dims[1],dims[2]), min(dims[1],dims[2])}); rot.push_back({dims[1], max(dims[0],dims[2]), min(dims[0],dims[2])}); rot.push_back({dims[2], max(dims[0],dims[1]), min(dims[0],dims[1])}); } sort(rot.begin(), rot.end(), cmp); int m = rot.size(); vector<int> dp(m); for (int i = 0; i < m; i++) dp[i] = rot[i].h; for (int i = 1; i < m; i++) for (int j = 0; j < i; j++) if (rot[i].w < rot[j].w && rot[i].d < rot[j].d) dp[i] = max(dp[i], dp[j] + rot[i].h); return *max_element(dp.begin(), dp.end()); }
Code โ Python
Python def max_stack_height(boxes): rotations = [] for h, w, d in boxes: dims = sorted([h, w, d]) rotations.append((dims[0], max(dims[1],dims[2]), min(dims[1],dims[2]))) rotations.append((dims[1], max(dims[0],dims[2]), min(dims[0],dims[2]))) rotations.append((dims[2], max(dims[0],dims[1]), min(dims[0],dims[1]))) rotations.sort(key=lambda x: x[1] * x[2], reverse=True) n = len(rotations) dp = [r[0] for r in rotations] for i in range(1, n): for j in range(i): if rotations[i][1] < rotations[j][1] and \ rotations[i][2] < rotations[j][2]: dp[i] = max(dp[i], dp[j] + rotations[i][0]) return max(dp) print(max_stack_height([(4,6,7), (1,2,3), (4,5,6)])) # Output: 22
Complexity
| Metric | Value |
|---|---|
| Time | O(nยฒ ร 9) = O(nยฒ) where n = number of box types (3n rotations) |
| Space | O(n) |
3. Integer Knapsack (0/1, Duplicate Items Forbidden)
๐ Problem Statement โ The CLASSIC DP Problem
Given n items, each with a weight w[i] and value v[i], and a knapsack with capacity W. Find the maximum total value of items that fit in the knapsack. Each item can be used at most once.
Example: Items = {(wt=1, val=1), (wt=3, val=4), (wt=4, val=5), (wt=5, val=7)}, Capacity W = 7.
Recurrence Relation
Recurrence dp[i][w] = maximum value using first i items with capacity w // Choice: include item i or exclude it dp[i][w] = max( dp[i-1][w], // don't take item i dp[i-1][w - wt[i]] + val[i] // take item i (if wt[i] โค w) ) // Base cases: dp[0][w] = 0 // 0 items โ 0 value dp[i][0] = 0 // 0 capacity โ 0 value
Full DP Table Trace โ 4 Items, W = 7
Items: (1,1), (3,4), (4,5), (5,7) โ (weight, value)
| Item\W | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|---|
| 0 (none) | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 1 (1,1) | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
| 2 (3,4) | 0 | 1 | 1 | 4 | 5 | 5 | 5 | 5 |
| 3 (4,5) | 0 | 1 | 1 | 4 | 5 | 6 | 6 | 9 |
| 4 (5,7) | 0 | 1 | 1 | 4 | 5 | 7 | 8 | 9 |
Answer: dp[4][7] = 9 (take items 2 and 3: weight 3+4=7, value 4+5=9) โ
Trace Walkthrough
Reading dp[4][7]=9: Can we include item 4 (wt=5, val=7)? dp[3][7-5]+7 = dp[3][2]+7 = 1+7 = 8. Don't include: dp[3][7]=9. So 9 > 8 โ don't take item 4. Move to dp[3][7]=9. Include item 3 (wt=4, val=5)? dp[2][7-4]+5 = dp[2][3]+5 = 4+5 = 9 โ . Take item 3. Move to dp[2][3]=4. Include item 2 (wt=3, val=4)? dp[1][0]+4 = 0+4 = 4 โ . Take item 2. Done โ Items {2, 3}.
Code โ C++
C++ int knapsack(int W, int wt[], int val[], int n) { vector<vector<int>> dp(n + 1, vector<int>(W + 1, 0)); for (int i = 1; i <= n; i++) { for (int w = 0; w <= W; w++) { dp[i][w] = dp[i-1][w]; // don't take item i if (wt[i-1] <= w) dp[i][w] = max(dp[i][w], dp[i-1][w - wt[i-1]] + val[i-1]); } } return dp[n][W]; }
Code โ Python
Python def knapsack(W, wt, val, n): dp = [[0] * (W + 1) for _ in range(n + 1)] for i in range(1, n + 1): for w in range(W + 1): dp[i][w] = dp[i-1][w] if wt[i-1] <= w: dp[i][w] = max(dp[i][w], dp[i-1][w - wt[i-1]] + val[i-1]) return dp[n][W] print(knapsack(7, [1,3,4,5], [1,4,5,7], 4)) # Output: 9
Space Optimization โ 1D Array
C++ int knapsack1D(int W, int wt[], int val[], int n) { vector<int> dp(W + 1, 0); for (int i = 0; i < n; i++) for (int w = W; w >= wt[i]; w--) // RIGHT TO LEFT! dp[w] = max(dp[w], dp[w - wt[i]] + val[i]); return dp[W]; }
Complexity
| Metric | 2D Version | 1D Optimized |
|---|---|---|
| Time | O(n ร W) | O(n ร W) |
| Space | O(n ร W) | O(W) |
4. Edit Distance (Levenshtein Distance)
โ๏ธ Problem Statement
Given two strings A and B, find the minimum number of operations to transform A into B. Allowed operations: Insert, Delete, Replace โ each costs 1.
Example: Transform "saturday" โ "sunday". Minimum operations = 3.
Applications: Spell checkers (Google "Did you mean?"), DNA sequence alignment, plagiarism detection, auto-correct on keyboards.
Recurrence Relation
Recurrence dp[i][j] = min edits to convert A[0..i-1] to B[0..j-1] if A[i-1] == B[j-1]: dp[i][j] = dp[i-1][j-1] // characters match, no operation else: dp[i][j] = 1 + min( dp[i-1][j], // DELETE from A dp[i][j-1], // INSERT into A dp[i-1][j-1] // REPLACE in A ) // Base cases: dp[i][0] = i // delete all i chars from A dp[0][j] = j // insert all j chars into empty A
Full DP Table Trace โ "saturday" โ "sunday"
| "" | s | u | n | d | a | y | |
|---|---|---|---|---|---|---|---|
| "" | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
| s | 1 | 0 | 1 | 2 | 3 | 4 | 5 |
| a | 2 | 1 | 1 | 2 | 3 | 3 | 4 |
| t | 3 | 2 | 2 | 2 | 3 | 4 | 4 |
| u | 4 | 3 | 2 | 3 | 3 | 4 | 5 |
| r | 5 | 4 | 3 | 3 | 4 | 4 | 5 |
| d | 6 | 5 | 4 | 4 | 3 | 4 | 5 |
| a | 7 | 6 | 5 | 5 | 4 | 3 | 4 |
| y | 8 | 7 | 6 | 6 | 5 | 4 | 3 |
Answer: Edit Distance = 3 (delete 'a', delete 't', replace 'r'โ'n') โ
Code โ C++
C++ int editDistance(string A, string B) { int m = A.size(), n = B.size(); vector<vector<int>> dp(m + 1, vector<int>(n + 1)); for (int i = 0; i <= m; i++) dp[i][0] = i; for (int j = 0; j <= n; j++) dp[0][j] = j; for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; j++) { if (A[i-1] == B[j-1]) dp[i][j] = dp[i-1][j-1]; else dp[i][j] = 1 + min({dp[i-1][j], dp[i][j-1], dp[i-1][j-1]}); } } return dp[m][n]; }
Code โ Python
Python def edit_distance(A, B): m, n = len(A), len(B) dp = [[0] * (n + 1) for _ in range(m + 1)] for i in range(m + 1): dp[i][0] = i for j in range(n + 1): dp[0][j] = j for i in range(1, m + 1): for j in range(1, n + 1): if A[i-1] == B[j-1]: dp[i][j] = dp[i-1][j-1] else: dp[i][j] = 1 + min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) return dp[m][n] print(edit_distance("saturday", "sunday")) # Output: 3
Complexity
| Metric | Value |
|---|---|
| Time | O(m ร n) |
| Space | O(m ร n) โ can be optimized to O(min(m, n)) using 2 rows |
5. Longest Increasing Subsequence (LIS)
๐ Problem Statement
Given an array of n integers, find the length of the longest subsequence where each element is strictly greater than the previous one. Elements need not be contiguous.
Example: Array = [10, 22, 9, 33, 21, 50, 41, 60, 80]. LIS = [10, 22, 33, 50, 60, 80], length = 6.
Approach 1: O(nยฒ) DP
Recurrence
Recurrence dp[i] = length of LIS ending at index i dp[i] = 1 + max(dp[j]) for all j < i where arr[j] < arr[i] // Base case: dp[i] = 1 for all i // every element is an LIS of length 1 Answer = max(dp[0], dp[1], ..., dp[n-1])
DP Table Trace
Array: [10, 22, 9, 33, 21, 50, 41, 60, 80]
| Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|---|
| arr[] | 10 | 22 | 9 | 33 | 21 | 50 | 41 | 60 | 80 |
| dp[] | 1 | 2 | 1 | 3 | 2 | 4 | 4 | 5 | 6 |
Trace for dp[5]=4: arr[5]=50. Check j=0: 10<50 โ dp[0]+1=2. j=1: 22<50 โ dp[1]+1=3. j=2: 9<50 โ 2. j=3: 33<50 โ dp[3]+1=4. j=4: 21<50 โ 3. Best = 4.
Answer: max(dp) = 6 โ
Code โ O(nยฒ) C++
C++ int lis_n2(int arr[], int n) { vector<int> dp(n, 1); for (int i = 1; i < n; i++) for (int j = 0; j < i; j++) if (arr[j] < arr[i]) dp[i] = max(dp[i], dp[j] + 1); return *max_element(dp.begin(), dp.end()); }
Approach 2: O(n log n) โ Patience Sorting with Binary Search
Intuition: Maintain an array tails[] where tails[i] = smallest tail element of all increasing subsequences of length i+1. For each new element, use binary search to find its position.
Code โ O(n log n) C++
C++ int lis_nlogn(int arr[], int n) { vector<int> tails; for (int i = 0; i < n; i++) { auto it = lower_bound(tails.begin(), tails.end(), arr[i]); if (it == tails.end()) tails.push_back(arr[i]); // extend longest subsequence else *it = arr[i]; // replace to keep smallest tail } return tails.size(); }
Code โ O(n log n) Python
Python from bisect import bisect_left def lis_nlogn(arr): tails = [] for x in arr: pos = bisect_left(tails, x) if pos == len(tails): tails.append(x) else: tails[pos] = x return len(tails) print(lis_nlogn([10,22,9,33,21,50,41,60,80])) # Output: 6
Complexity Comparison
| Approach | Time | Space | When to Use |
|---|---|---|---|
| O(nยฒ) DP | O(nยฒ) | O(n) | n โค 5000, need actual subsequence |
| O(n log n) Binary Search | O(n log n) | O(n) | n โค 10โถ, competitive programming |
6. Longest Common Subsequence (LCS)
๐ Problem Statement
Given two strings X and Y, find the length of the longest subsequence common to both. A subsequence is obtained by deleting some (or zero) characters without changing order.
Example: X = "ABCBDAB", Y = "BDCAB". LCS = "BCAB", length = 4.
Recurrence Relation
Recurrence dp[i][j] = LCS of X[0..i-1] and Y[0..j-1] if X[i-1] == Y[j-1]: dp[i][j] = dp[i-1][j-1] + 1 // characters match else: dp[i][j] = max(dp[i-1][j], dp[i][j-1]) // skip one char from either // Base cases: dp[0][j] = 0 // empty X dp[i][0] = 0 // empty Y
Full DP Table Trace โ "ABCBDAB" and "BDCAB"
| "" | B | D | C | A | B | |
|---|---|---|---|---|---|---|
| "" | 0 | 0 | 0 | 0 | 0 | 0 |
| A | 0 | 0 | 0 | 0 | 1 | 1 |
| B | 0 | 1 | 1 | 1 | 1 | 1 |
| C | 0 | 1 | 1 | 2 | 2 | 2 |
| B | 0 | 1 | 1 | 2 | 2 | 2 |
| D | 0 | 1 | 2 | 2 | 2 | 2 |
| A | 0 | 1 | 2 | 2 | 3 | 3 |
| B | 0 | 1 | 2 | 2 | 3 | 4 |
Answer: LCS length = 4 โ
Backtracking to Find Actual Subsequence
Start at dp[7][5]=4. X[6]='B'==Y[4]='B' โ take 'B', go to dp[6][4]. X[5]='A'==Y[3]='A' โ take 'A', go to dp[5][3]. X[4]='D'โ Y[2]='C', dp[4][3]>dp[5][2] โ go left dp[5][2]. X[4]='D'==Y[1]='D' โ wait, we're at (5,2). X[4]='D'==Y[1]='D' โ take 'D', go to dp[4][1]. dp[3][1]=1, X[2]='C'โ Y[0]='B', go up. dp[2][1]=1, X[1]='B'==Y[0]='B' โ take 'B', go to dp[1][0]=0. Done.
LCS (reversed): B, D, A, B โ "BDAB" (one of the valid LCS; "BCAB" is another)
Code โ C++
C++ int lcs(string X, string Y) { int m = X.size(), n = Y.size(); vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0)); for (int i = 1; i <= m; i++) for (int j = 1; j <= n; j++) if (X[i-1] == Y[j-1]) dp[i][j] = dp[i-1][j-1] + 1; else dp[i][j] = max(dp[i-1][j], dp[i][j-1]); return dp[m][n]; } // Backtrack to find actual LCS: string findLCS(string X, string Y, vector<vector<int>>& dp) { string result = ""; int i = X.size(), j = Y.size(); while (i > 0 && j > 0) { if (X[i-1] == Y[j-1]) { result = X[i-1] + result; i--; j--; } else if (dp[i-1][j] > dp[i][j-1]) i--; else j--; } return result; }
Code โ Python
Python def lcs(X, Y): m, n = len(X), len(Y) dp = [[0] * (n + 1) for _ in range(m + 1)] for i in range(1, m + 1): for j in range(1, n + 1): if X[i-1] == Y[j-1]: dp[i][j] = dp[i-1][j-1] + 1 else: dp[i][j] = max(dp[i-1][j], dp[i][j-1]) return dp[m][n] print(lcs("ABCBDAB", "BDCAB")) # Output: 4
Complexity
| Metric | Value |
|---|---|
| Time | O(m ร n) |
| Space | O(m ร n) โ can optimize to O(min(m,n)) |
7. Balanced Partition Problem
โ๏ธ Problem Statement
Given an array of positive integers, partition it into two subsets such that the difference of their sums is minimized.
Example: Array = [1, 6, 11, 5]. Total = 23. Best partition: {1, 5, 6} (sum=12) and {11} (sum=11). Minimum difference = 1.
Reduction to Subset Sum
If total sum = S, we want to find a subset with sum as close to S/2 as possible. If we find the largest achievable sum โค S/2, call it maxSum, then minimum difference = S โ 2 ร maxSum. This reduces to the Subset Sum problem.
Recurrence
Recurrence dp[i][j] = true if a subset of first i elements can sum to j dp[i][j] = dp[i-1][j] // don't include arr[i-1] OR dp[i-1][j - arr[i-1]] // include arr[i-1] // Base cases: dp[i][0] = true // empty subset sums to 0 dp[0][j] = false // no elements, can't make non-zero sum Answer: Find largest j โค S/2 where dp[n][j] == true Min difference = S - 2*j
Code โ C++
C++ int balancedPartition(int arr[], int n) { int S = 0; for (int i = 0; i < n; i++) S += arr[i]; vector<vector<bool>> dp(n + 1, vector<bool>(S / 2 + 1, false)); for (int i = 0; i <= n; i++) dp[i][0] = true; for (int i = 1; i <= n; i++) for (int j = 1; j <= S / 2; j++) { dp[i][j] = dp[i-1][j]; if (arr[i-1] <= j) dp[i][j] = dp[i][j] || dp[i-1][j - arr[i-1]]; } for (int j = S / 2; j >= 0; j--) if (dp[n][j]) return S - 2 * j; return S; }
Code โ Python
Python def balanced_partition(arr): S = sum(arr) n = len(arr) half = S // 2 dp = [[False] * (half + 1) for _ in range(n + 1)] for i in range(n + 1): dp[i][0] = True for i in range(1, n + 1): for j in range(1, half + 1): dp[i][j] = dp[i-1][j] if arr[i-1] <= j: dp[i][j] = dp[i][j] or dp[i-1][j - arr[i-1]] for j in range(half, -1, -1): if dp[n][j]: return S - 2 * j print(balanced_partition([1, 6, 11, 5])) # Output: 1
Complexity
| Metric | Value |
|---|---|
| Time | O(n ร S/2) |
| Space | O(n ร S/2) โ can optimize to O(S/2) with 1D array |
Learn by Doing โ 3-Tier Lab Structure
๐ข Tier 1 โ GUIDED: Longest Increasing Subsequence (LIS)
Step 1: Understand the Input
Array: [3, 10, 2, 1, 20]. Expected LIS length = 3 (e.g., [3, 10, 20]).
Step 2: Initialize DP Array
Create dp[5] = {1, 1, 1, 1, 1} โ every element is an LIS of length 1 by itself.
Step 3: Fill the DP Table
i=1 (arr[1]=10): j=0: arr[0]=3 < 10 โ dp[1] = max(1, dp[0]+1) = 2.
i=2 (arr[2]=2): j=0: 3>2 skip. j=1: 10>2 skip. dp[2]=1.
i=3 (arr[3]=1): All previous โฅ 1. dp[3]=1.
i=4 (arr[4]=20): j=0: 3<20 โ dp[4]=max(1,2)=2. j=1: 10<20 โ max(2,3)=3. j=2: 2<20 โ max(3,2)=3. j=3: 1<20 โ max(3,2)=3. dp[4]=3.
Step 4: Code It
Python def lis(arr): n = len(arr) dp = [1] * n for i in range(1, n): for j in range(i): if arr[j] < arr[i]: dp[i] = max(dp[i], dp[j] + 1) return max(dp) print(lis([3, 10, 2, 1, 20])) # Output: 3
Step 5: Test with More Inputs
Try: [50, 3, 10, 7, 40, 80] โ Expected: 4 ([3, 10, 40, 80])
Try: [5, 4, 3, 2, 1] โ Expected: 1 (decreasing array)
๐ก Tier 2 โ SEMI-GUIDED: 0/1 Knapsack Problem
Your Mission:
Implement the 0/1 Knapsack for: Items = [(2,3), (3,4), (4,5), (5,6)], Capacity = 8. Find max value.
Hints:
- Create a 2D DP table of size (n+1) ร (W+1), initialized to 0
- For each item i and weight w, decide: include or exclude?
- If
wt[i] > w, you can't include โ dp[i][w] = dp[i-1][w] - Otherwise, take max of including vs excluding
- After filling the table, backtrack to find which items were selected
๐ด Tier 3 โ OPEN CHALLENGE: Edit Distance with Backtracking
The Challenge:
- Implement Edit Distance for any two strings
- After computing the DP table, backtrack to print the exact sequence of operations (Insert/Delete/Replace)
- Handle edge cases: empty strings, identical strings, single character strings
- Optimize space to O(min(m,n)) using 2 rows
- Test with: "intention" โ "execution" (expected: 5)
Problem Set โ Tracing, Programming & Interview Problems
Part 1: DP Table Tracing (5 Problems)
T1. Fill the complete DP table for LCS of X="AGGTAB" and Y="GXTXAYB". What is the LCS length?
T2. Trace the 0/1 Knapsack DP table for items [(1,1), (2,6), (3,10), (5,15), (7,16)] with W=8. Which items are selected?
T3. Fill the Edit Distance DP table for "kitten" โ "sitting". Show all 7ร8 = 56 cells.
T4. Trace the LIS dp[] array for [0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15]. What is the LIS length?
T5. Fill the Binomial Coefficient table (Pascal's Triangle) for n=8, and find C(8,3) and C(8,5). Verify C(8,3) = C(8,5).
Part 2: Programming Problems (8 Problems)
P1. Beginner Implement LIS with O(nยฒ) DP. Input: array of integers. Output: LIS length.
P2. Beginner Implement LCS for two strings. Print both the length and the actual subsequence.
P3. Intermediate Implement 0/1 Knapsack with backtracking to print selected items.
P4. Intermediate Implement Edit Distance and print the sequence of operations to transform A to B.
P5. Intermediate Implement Box Stacking. Handle all 3 rotations correctly. Print the stack order.
P6. Intermediate Implement Balanced Partition and print both subsets.
P7. Advanced Implement LIS in O(n log n) using binary search. Reconstruct the actual subsequence (not just length).
P8. Advanced Given a grid of mรn with positive integers, find the path from top-left to bottom-right with minimum sum (move only right or down). Use DP.
Part 3: Industry Application Problems (3 Problems)
๐ญ I1. Flipkart Delivery Optimization
A delivery truck has capacity 500 kg. There are 20 packages with given weights and "priority scores" (value). Use 0/1 Knapsack to select packages that maximize total priority within the weight limit. Implement and test with realistic data.
๐งฌ I2. DNA Sequence Alignment
Given two DNA sequences (strings of A, C, G, T), use LCS to find the longest matching subsequence. Then use Edit Distance to compute the mutation distance. Compare results for: "ACGTACGT" and "ACGTAGCT".
๐ I3. Auto-Correct System
Given a dictionary of 100 common English words, build a spell-checker. For any misspelled input, output the 3 closest words by Edit Distance. Handle tie-breaking alphabetically.
Part 4: Interview Questions (3 Problems)
๐ผ Asked at Amazon โ Coin Change Problem
Given coins of denominations [1, 5, 10, 25] and a target amount, find the minimum number of coins needed. This is unbounded knapsack variant. Follow-up: What if you also need to print which coins were used?
๐ผ Asked at Google โ Longest Palindromic Subsequence
Given a string, find the length of its longest palindromic subsequence. Hint: This is LCS of the string with its reverse. Example: "BBABCBCAB" โ LPS = "BABCBAB", length = 7.
๐ผ Asked at Microsoft โ Wildcard Pattern Matching
Given a string and a pattern with '?' (matches single char) and '*' (matches any sequence), determine if the pattern matches the string. Use DP. Example: "adceb" matches "*a*b" โ true. "acdcb" matches "a*c?b" โ false.
MCQ Assessment Bank โ 30 Questions (Bloom's Mapped)
Remember / Identify (Q1โQ5)
What are the two key properties required for a problem to be solvable by Dynamic Programming?
- Greedy choice and optimal substructure
- Optimal substructure and overlapping subproblems
- Divide and conquer and memoization
- Recursion and iteration
The recurrence for Binomial Coefficient C(n, k) using Pascal's Triangle is:
- C(n,k) = C(n-1,k-1) ร C(n-1,k)
- C(n,k) = C(n-1,k-1) + C(n-1,k)
- C(n,k) = C(n,k-1) + C(n-1,k)
- C(n,k) = n! / (k! ร (n-k)!)
In the 0/1 Knapsack problem, what does "0/1" signify?
- Items have binary weights (0 or 1)
- Each item can be taken at most once (either 0 or 1 times)
- The knapsack has only 0 or 1 capacity
- There are only 2 items to choose from
The three operations allowed in Edit Distance are:
- Insert, Delete, Swap
- Insert, Delete, Replace
- Add, Remove, Modify
- Push, Pop, Replace
What is the base case for LIS (dp[i])?
- dp[i] = 0 for all i
- dp[i] = 1 for all i
- dp[0] = 1, rest are 0
- dp[i] = arr[i] for all i
Understand / Explain (Q6โQ10)
Why can't we use a greedy approach for the 0/1 Knapsack problem?
- Greedy is always slower than DP
- Taking the item with best value/weight ratio doesn't guarantee optimal solution when items can't be split
- Greedy can't handle arrays
- 0/1 Knapsack has no optimal substructure
In the LCS recurrence, when X[i-1] โ Y[j-1], why do we take max(dp[i-1][j], dp[i][j-1])?
- We delete both characters
- We try skipping one character from either string and take the better result
- We replace one character
- We insert a character
Why does the Box Stacking problem reduce to LIS?
- Both problems have the same time complexity
- Both use 1D DP arrays
- After sorting by base area and generating rotations, finding max stack is like finding longest increasing chain of compatible boxes
- LIS is always used for stacking problems
What does the cell dp[i][j] represent in the Edit Distance table?
- The number of common characters between A[0..i] and B[0..j]
- The minimum operations to convert A[0..i-1] to B[0..j-1]
- The maximum operations needed
- The edit distance of the remaining characters
In the Balanced Partition problem, why do we only need to find subset sum up to S/2?
- No subset can have sum greater than S/2
- If one subset has sum โค S/2 and is as close to S/2 as possible, it minimizes the difference with the other subset
- We always split arrays in half
- S/2 is the average element value
Apply / Compute (Q11โQ15)
What is C(6, 2) using Pascal's Triangle?
- 12
- 15
- 20
- 30
For arr = [5, 1, 4, 2, 8], what is the LIS length?
- 2
- 3
- 4
- 5
What is the Edit Distance between "cat" and "cut"?
- 0
- 1
- 2
- 3
For Knapsack with items [(wt=2, val=3), (wt=3, val=4)] and capacity W=4, what is the max value?
- 3
- 4
- 7
- 0
What is the LCS length of "ABC" and "AC"?
- 1
- 2
- 3
- 0
Analyze / Compare (Q16โQ20)
What is the key advantage of the O(n log n) LIS algorithm over the O(nยฒ) approach?
- It uses less memory
- It handles negative numbers
- It scales to arrays of size 10โถ+ where O(nยฒ) would TLE
- It's easier to implement
In 1D Knapsack space optimization, why must we iterate weights from W down to wt[i]?
- It's faster than left to right
- To avoid using the same item multiple times in the same row
- The compiler optimizes it better
- The recurrence requires it mathematically
Which DP problem has the same recurrence structure as Balanced Partition?
- LCS
- Edit Distance
- Subset Sum / 0/1 Knapsack
- LIS
If we apply Edit Distance to strings of length m=1000 and n=1000, approximately how many operations are performed?
- 1,000
- 2,000
- 1,000,000
- 1,000,000,000
Compare top-down (memoization) vs bottom-up (tabulation) DP. Which is true?
- Top-down is always faster
- Bottom-up avoids recursion overhead and stack overflow risks
- They always compute exactly the same set of subproblems
- Top-down cannot be space optimized
Evaluate / Judge (Q21โQ25)
A student claims: "The 0/1 Knapsack problem has O(nW) time complexity, so it's polynomial time." Is this correct?
- Yes, O(nW) is clearly polynomial
- No, it's pseudo-polynomial because W is exponential in the number of bits needed to represent it
- No, it's exponential
- Yes, because n and W are both linear
A developer uses Edit Distance to compare 10-million-character DNA sequences. Is this approach practical?
- Yes, Edit Distance works for any size input
- No โ O(mรn) = O(10ยนโด) operations would take years. Specialized bioinformatics algorithms (BLAST, Smith-Waterman) are needed.
- Yes, with memoization it will be fast enough
- No, Edit Distance only works for short strings
Is it possible to reconstruct the actual LIS (not just its length) from the O(n log n) algorithm?
- No, the tails array doesn't store the actual subsequence
- Yes, by maintaining a parent/predecessor array alongside the tails array
- Only with the O(nยฒ) algorithm
- Yes, the tails array IS the LIS
Evaluate: "Balanced Partition always produces two subsets of equal size." True or False?
- True โ that's what "balanced" means
- False โ "balanced" refers to minimizing the sum difference, not the count of elements
- True โ it's required by the algorithm
- It depends on the array size
Is a greedy approach correct for Box Stacking? (Always pick the box with the largest base area first)
- Yes, larger base area always supports more weight
- No โ a box with a larger area might have a shorter height, and skipping it could allow stacking more boxes with greater total height
- Yes, greedy is optimal for any stacking problem
- It depends on the number of boxes
Create / Design (Q26โQ30)
To find the Longest Palindromic Subsequence (LPS) of a string S, which DP strategy works?
- Apply LIS on the string's ASCII values
- Compute LCS of S and reverse(S)
- Use Edit Distance of S with reverse(S)
- Apply Knapsack with character weights
How would you modify 0/1 Knapsack to handle items with both weight AND volume constraints?
- Use a 2D Knapsack: dp[i][w][v] โ add a volume dimension
- Sum weight and volume into a single constraint
- Run Knapsack twice โ once for weight, once for volume
- It's impossible with DP
How would you use Edit Distance to implement an autocomplete feature?
- Compute Edit Distance of input prefix against all dictionary words and suggest the closest ones
- Use LCS instead of Edit Distance
- Use binary search on the dictionary
- Edit Distance can't be used for autocomplete
To solve "Minimum number of coins to make change for amount N," which DP formulation is correct?
- dp[i] = min(dp[i-coin] + 1) for each coin denomination โ unbounded knapsack variant
- dp[i] = dp[i-1] + dp[i-2]
- Use LIS on coin denominations
- Use Edit Distance on coin values
Design a DP solution for: "Given a rod of length n and prices for each length 1..n, maximize revenue by cutting the rod." What's the state?
- dp[i] = max revenue from rod of length i
- dp[i][j] = revenue from cutting at position i,j
- dp[i] = number of cuts for length i
- dp[i][j] = LCS of cuts
Short Answer Questions (8 Questions)
SA1. What are the two essential properties of Dynamic Programming? Explain each with a brief example.
Answer:
1. Optimal Substructure: An optimal solution to the problem contains optimal solutions to its subproblems. Example: In shortest path, if the shortest AโC path goes through B, then the AโB and BโC segments must also be shortest paths.
2. Overlapping Subproblems: The same subproblems are solved repeatedly. Example: In Fibonacci, fib(5) calls fib(4) and fib(3), but fib(4) also calls fib(3) โ fib(3) is computed twice. DP stores it once.
SA2. Write the recurrence relation for Edit Distance and explain each case.
Answer:
dp[i][j] = min edits to convert A[0..i-1] to B[0..j-1].
โข If A[i-1] == B[j-1]: dp[i][j] = dp[i-1][j-1] โ characters match, no operation needed.
โข Else: dp[i][j] = 1 + min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1])
- dp[i-1][j] + 1: Delete A[i-1]
- dp[i][j-1] + 1: Insert B[j-1] into A
- dp[i-1][j-1] + 1: Replace A[i-1] with B[j-1]
SA3. Explain how the Box Stacking problem reduces to LIS with an example.
Answer:
1. Generate all rotations of each box (3 per box, using each dimension as height). 2. Sort by base area (decreasing). 3. Now finding the tallest stack = finding a chain of boxes where each box's width AND depth are strictly smaller than the box below. This is exactly the LIS problem where the "increasing" condition is on two dimensions (width and depth) instead of one. The DP is dp[i] = max height ending at box i.
SA4. Differentiate between top-down memoization and bottom-up tabulation in DP.
Answer:
Top-down (Memoization): Start from the original problem, recursively break into subproblems, cache results. Uses recursion + hash map/array. May not solve all subproblems. Risk of stack overflow for deep recursion.
Bottom-up (Tabulation): Start from smallest subproblems, iteratively build up to the answer. Uses iteration + table. Solves all subproblems. No stack overflow risk. Easier to space-optimize.
SA5. Why is 0/1 Knapsack NP-hard despite having O(nW) time complexity?
Answer:
O(nW) is pseudo-polynomial, not truly polynomial. The input size is measured in bits: W requires logโ(W) bits to represent. If W = 2ยณโฐ (~10โน), the input size is only 30 bits, but O(nW) = O(n ร 10โน) โ exponential in the input size. A truly polynomial algorithm would be O(n ร polylog(W)), which is not known to exist for Knapsack.
SA6. Explain the space optimization technique for converting 2D DP to 1D in Knapsack.
Answer:
In 2D Knapsack, dp[i][w] only depends on row i-1. So we only need 2 rows, or even 1 row if we iterate weights right-to-left. When computing dp[w] for item i, the values dp[w-wt[i]] haven't been updated yet (still contain row i-1 values). This ensures each item is used at most once. Iterate from W down to wt[i] to preserve this invariant.
SA7. What is the time complexity of the O(n log n) LIS algorithm, and how does binary search help?
Answer:
The algorithm maintains a tails array where tails[i] = smallest tail element of all increasing subsequences of length i+1. For each new element, binary search (O(log n)) finds its position: either extend the array or replace an existing element. n elements ร O(log n) each = O(n log n) total. The key insight: the tails array is always sorted, enabling binary search.
SA8. Name three real-world applications each for Edit Distance and LCS.
Answer:
Edit Distance: (1) Spell-checkers ("Did you mean?"), (2) DNA mutation analysis in bioinformatics, (3) Plagiarism detection in academic papers.
LCS: (1) Diff tools (git diff) for comparing file versions, (2) DNA/protein sequence alignment (NCBI BLAST), (3) Recommendation systems (Netflix: finding similar watch histories).
Long Answer Questions (3 Questions)
LA1. Explain the 0/1 Knapsack problem. Derive the recurrence, trace the DP table for items {(2,3), (3,4), (4,5), (5,6)} with W=5, and write C++ code. (12 marks)
Problem: Given n items with weights and values, maximize value within capacity W. Each item used at most once.
Recurrence: dp[i][w] = max(dp[i-1][w], dp[i-1][w-wt[i-1]] + val[i-1]) if wt[i-1] โค w, else dp[i][w] = dp[i-1][w].
Base cases: dp[0][w] = 0, dp[i][0] = 0.
DP Table:
| i\w | 0 | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|---|
| 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 1(2,3) | 0 | 0 | 3 | 3 | 3 | 3 |
| 2(3,4) | 0 | 0 | 3 | 4 | 4 | 7 |
| 3(4,5) | 0 | 0 | 3 | 4 | 5 | 7 |
| 4(5,6) | 0 | 0 | 3 | 4 | 5 | 7 |
Answer: 7 (items 1 and 2: weight 2+3=5, value 3+4=7)
C++ Code:
C++ int knapsack(int W, int wt[], int val[], int n) { int dp[n+1][W+1]; memset(dp, 0, sizeof(dp)); for(int i=1; i<=n; i++) for(int w=1; w<=W; w++) { dp[i][w] = dp[i-1][w]; if(wt[i-1] <= w) dp[i][w] = max(dp[i][w], dp[i-1][w-wt[i-1]] + val[i-1]); } return dp[n][W]; }
LA2. Explain the Longest Common Subsequence (LCS) problem. Derive the recurrence, trace the full DP table for X="AXYT" and Y="AYZX", perform backtracking to find the actual LCS, and analyze complexity. (12 marks)
Problem: Find the longest subsequence common to both strings.
Recurrence: If X[i-1]==Y[j-1]: dp[i][j] = dp[i-1][j-1]+1. Else: dp[i][j] = max(dp[i-1][j], dp[i][j-1]).
DP Table for "AXYT" vs "AYZX":
| "" | A | Y | Z | X | |
|---|---|---|---|---|---|
| "" | 0 | 0 | 0 | 0 | 0 |
| A | 0 | 1 | 1 | 1 | 1 |
| X | 0 | 1 | 1 | 1 | 2 |
| Y | 0 | 1 | 2 | 2 | 2 |
| T | 0 | 1 | 2 | 2 | 2 |
LCS length = 2. Backtracking: dp[4][4]=2. Tโ X, go up. dp[3][4]=2. Yโ X, dp[2][4]โฅdp[3][3] โ go up. dp[2][4]=2. X==X โ take 'X', go to dp[1][3]=1. dp[1][3]=1. Aโ Z, go left. dp[1][2]=1. Aโ Y, go left. dp[1][1]=1. A==A โ take 'A'. LCS = "AX".
Time complexity: O(mรn). Space: O(mรn), optimizable to O(min(m,n)).
LA3. Compare and contrast the following DP problems: LIS, LCS, and Edit Distance. For each, state the problem, recurrence, time complexity, and one real-world application. (12 marks)
| Aspect | LIS | LCS | Edit Distance |
|---|---|---|---|
| Input | Single array | Two strings | Two strings |
| Goal | Longest increasing subsequence | Longest common subsequence | Min operations to transform |
| DP State | dp[i] = LIS ending at i | dp[i][j] = LCS of prefixes | dp[i][j] = min edits for prefixes |
| Dimension | 1D | 2D | 2D |
| Time | O(nยฒ) or O(n log n) | O(mรn) | O(mรn) |
| Space | O(n) | O(mรn) | O(mรn) |
| Application | Stock trading (longest price increase run) | Git diff, DNA alignment | Spell checker, autocorrect |
Key insight: LCS and Edit Distance are closely related. For strings of same length n: Edit Distance = n โ LCS length (when only insert/delete allowed). All three share the DP methodology: define states, establish transitions, fill table, extract answer.
Lab Programs โ Complete Code, Output & Viva Questions
Lab 1: Longest Increasing Subsequence (LIS)
C++ #include <bits/stdc++.h> using namespace std; int main() { int n; cout << "Enter array size: "; cin >> n; vector<int> arr(n), dp(n, 1); cout << "Enter elements: "; for (int i = 0; i < n; i++) cin >> arr[i]; for (int i = 1; i < n; i++) for (int j = 0; j < i; j++) if (arr[j] < arr[i]) dp[i] = max(dp[i], dp[j] + 1); cout << "LIS Length: " << *max_element(dp.begin(), dp.end()); return 0; }
Viva Questions
- Q: What is the time complexity of LIS using DP?
A: O(nยฒ) โ two nested loops. - Q: Can LIS be solved in better than O(nยฒ)?
A: Yes, O(n log n) using patience sorting with binary search. - Q: What is the space complexity?
A: O(n) for the dp array. - Q: Is the LIS unique?
A: Not necessarily. Multiple subsequences can have the same maximum length. - Q: How would you print the actual LIS, not just its length?
A: Maintain a predecessor/parent array. After finding the max dp[i], backtrack using the parent array.
Lab 2: Longest Common Subsequence (LCS)
C++ #include <bits/stdc++.h> using namespace std; int main() { string X, Y; cout << "Enter string X: "; cin >> X; cout << "Enter string Y: "; cin >> Y; int m = X.size(), n = Y.size(); vector<vector<int>> dp(m+1, vector<int>(n+1, 0)); for (int i = 1; i <= m; i++) for (int j = 1; j <= n; j++) if (X[i-1] == Y[j-1]) dp[i][j] = dp[i-1][j-1] + 1; else dp[i][j] = max(dp[i-1][j], dp[i][j-1]); // Backtrack to find LCS string string lcs = ""; int i = m, j = n; while (i > 0 && j > 0) { if (X[i-1] == Y[j-1]) { lcs = X[i-1] + lcs; i--; j--; } else if (dp[i-1][j] > dp[i][j-1]) i--; else j--; } cout << "LCS Length: " << dp[m][n] << endl; cout << "LCS: " << lcs << endl; return 0; }
Viva Questions
- Q: What's the difference between subsequence and substring?
A: Subsequence = not necessarily contiguous. Substring = contiguous. - Q: Time and space complexity?
A: O(mรn) time and space. - Q: How is LCS related to diff tools?
A: Git diff uses LCS to find unchanged lines between file versions. - Q: Can LCS be used to find Longest Palindromic Subsequence?
A: Yes! LPS(S) = LCS(S, reverse(S)). - Q: How to optimize space?
A: Use only 2 rows (current and previous), O(min(m,n)) space.
Lab 3: Binomial Coefficient C(n, k)
Python def binomial(n, k): dp = [0] * (k + 1) dp[0] = 1 for i in range(1, n + 1): for j in range(min(i, k), 0, -1): dp[j] = dp[j] + dp[j - 1] return dp[k] n, k = map(int, input("Enter n and k: ").split()) print(f"C({n},{k}) = {binomial(n, k)}")
Viva Questions
- Q: Why not just use the factorial formula n!/(k!(n-k)!)?
A: Factorials overflow quickly for large n. DP avoids overflow by adding smaller numbers. - Q: What is Pascal's Triangle?
A: A triangular array where each entry is the sum of the two above it. Row n, column k gives C(n,k). - Q: Time and space of optimized version?
A: O(nk) time, O(k) space. - Q: Why iterate right to left in the 1D version?
A: To use previous row's values before overwriting them. - Q: What does C(n,k) represent combinatorially?
A: Number of ways to choose k items from n items, regardless of order.
Lab 4: Box Stacking Problem
Python def max_stack_height(boxes): rot = [] for h, w, d in boxes: dims = sorted([h, w, d]) # Generate all 3 rotations rot.append((dims[0], max(dims[1],dims[2]), min(dims[1],dims[2]))) rot.append((dims[1], max(dims[0],dims[2]), min(dims[0],dims[2]))) rot.append((dims[2], max(dims[0],dims[1]), min(dims[0],dims[1]))) rot.sort(key=lambda x: x[1]*x[2], reverse=True) n = len(rot) dp = [r[0] for r in rot] for i in range(1, n): for j in range(i): if rot[i][1] < rot[j][1] and rot[i][2] < rot[j][2]: dp[i] = max(dp[i], dp[j] + rot[i][0]) return max(dp) boxes = [(4,6,7), (1,2,3), (4,5,6)] print(f"Max stack height: {max_stack_height(boxes)}")
Viva Questions
- Q: Why do we generate 3 rotations per box?
A: Each dimension can be the height. The base is formed by the other two dimensions. - Q: Why sort by base area?
A: Ensures that when checking box i against box j (j < i), box j has a larger base area. This is necessary for the LIS-like DP to work correctly. - Q: What is the strict condition for stacking?
A: Both width AND depth of the top box must be strictly less than the bottom box. - Q: Can the same box type be used multiple times?
A: Yes, because different rotations of the same box are treated as different boxes. - Q: Time complexity?
A: O((3n)ยฒ) = O(9nยฒ) = O(nยฒ) where n is the number of box types.
Lab 5: Integer Knapsack (0/1)
C++ #include <bits/stdc++.h> using namespace std; int main() { int n, W; cout << "Enter number of items and capacity: "; cin >> n >> W; vector<int> wt(n), val(n); cout << "Enter weight and value for each item:\n"; for (int i = 0; i < n; i++) cin >> wt[i] >> val[i]; vector<vector<int>> dp(n+1, vector<int>(W+1, 0)); for (int i = 1; i <= n; i++) for (int w = 1; w <= W; w++) { dp[i][w] = dp[i-1][w]; if (wt[i-1] <= w) dp[i][w] = max(dp[i][w], dp[i-1][w-wt[i-1]] + val[i-1]); } cout << "Maximum value: " << dp[n][W] << endl; // Backtrack to find selected items cout << "Selected items: "; int w = W; for (int i = n; i > 0; i--) if (dp[i][w] != dp[i-1][w]) { cout << i << " "; w -= wt[i-1]; } return 0; }
Viva Questions
- Q: What's the difference between 0/1 and unbounded knapsack?
A: 0/1: each item at most once. Unbounded: items can be repeated. - Q: How do you backtrack to find selected items?
A: Start at dp[n][W]. If dp[i][w] โ dp[i-1][w], item i was included; subtract its weight and move to i-1. - Q: Time complexity?
A: O(n ร W). - Q: Is Knapsack polynomial or pseudo-polynomial?
A: Pseudo-polynomial. W can be exponentially large relative to its bit representation. - Q: How does 1D optimization work?
A: Iterate weights right to left using a single 1D array of size W+1.
Lab 6: Edit Distance (Levenshtein)
Python def edit_distance(A, B): m, n = len(A), len(B) dp = [[0]*(n+1) for _ in range(m+1)] for i in range(m+1): dp[i][0] = i for j in range(n+1): dp[0][j] = j for i in range(1, m+1): for j in range(1, n+1): if A[i-1] == B[j-1]: dp[i][j] = dp[i-1][j-1] else: dp[i][j] = 1 + min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) return dp[m][n] A = input("Enter string A: ") B = input("Enter string B: ") print(f"Edit Distance: {edit_distance(A, B)}")
Viva Questions
- Q: What are the three operations in Edit Distance?
A: Insert, Delete, Replace โ each costs 1. - Q: What's the base case?
A: dp[i][0] = i (delete all), dp[0][j] = j (insert all). - Q: Real-world application?
A: Spell checkers, DNA alignment, plagiarism detection, autocorrect. - Q: Can operations have different costs?
A: Yes! Weighted Edit Distance assigns different costs to insert/delete/replace. - Q: How to print the actual operations?
A: Backtrack from dp[m][n]: if diagonal with change โ replace, if from above โ delete, if from left โ insert.
Lab 7: Balanced Partition
C++ #include <bits/stdc++.h> using namespace std; int main() { int n; cout << "Enter array size: "; cin >> n; vector<int> arr(n); cout << "Enter elements: "; int S = 0; for (int i = 0; i < n; i++) { cin >> arr[i]; S += arr[i]; } int half = S / 2; vector<bool> dp(half + 1, false); dp[0] = true; for (int i = 0; i < n; i++) for (int j = half; j >= arr[i]; j--) dp[j] = dp[j] || dp[j - arr[i]]; for (int j = half; j >= 0; j--) if (dp[j]) { cout << "Minimum difference: " << S - 2 * j << endl; break; } return 0; }
Viva Questions
- Q: How does Balanced Partition relate to Subset Sum?
A: It reduces to finding a subset with sum closest to total/2. - Q: Why iterate right to left in 1D optimization?
A: To ensure each element is considered only once (0/1 constraint). - Q: What's the minimum possible difference?
A: 0 if total sum is even and an exact half-split exists, otherwise 1 (if odd sum). - Q: Time complexity?
A: O(n ร S/2) where S is the total sum. - Q: Can this handle negative numbers?
A: Standard DP can't. Need offset or different formulation for negative values.
Industry Spotlight โ A Day in the Life
๐ฉโ๐ป Kavitha Rajan, 28 โ SDE-II at Amazon India, Hyderabad
Background: B.Tech in CSE from NIT Trichy. Started competitive programming on CodeChef in 2nd year. Became 5-star rated (2100+). Cracked Amazon's interview in final year โ 2 out of 4 interview rounds featured DP problems (Knapsack variant and LCS).
A Typical Day:
9:00 AM โ Morning standup with the Fulfillment Optimization team. Review metrics: package throughput, delivery route efficiency, warehouse packing utilization.
10:00 AM โ Working on a box-packing optimization algorithm for Amazon's Hyderabad warehouse. Uses a variant of the Box Stacking / Bin Packing problem to minimize wasted space in delivery trucks. "We saved 12% on shipping costs last quarter with our DP-based packer."
12:00 PM โ Code review for a teammate's implementation of a dynamic allocation algorithm. "I always check if the DP recurrence matches the problem's optimal substructure. That's where bugs hide."
2:00 PM โ Interview panel: conducting technical interviews for SDE-I candidates. "I always ask at least one DP question โ usually LIS or Edit Distance. It separates candidates who memorize from those who truly understand state transitions."
4:00 PM โ Profiling and optimizing a DP-based recommendation engine. Reduced memory from O(mรn) to O(n) using space optimization โ exactly the technique from LCS.
6:00 PM โ Mentoring session with 2 interns. Teaching them to think in DP: "First define your states. Then write the recurrence on paper. Only then code."
| Detail | Info |
|---|---|
| Tools Used Daily | C++, Java, Python, AWS services, internal DP/optimization libraries |
| Entry Salary (SDE-I) | โน20โ30 LPA (Amazon India) |
| Current Salary (SDE-II, 4 yrs) | โน38โ50 LPA (including RSUs) |
| Interview Tip | "Master these 7 DP problems. They cover 80% of DP interview questions at FAANG." |
| Companies Using DP | Amazon, Google, Microsoft, Flipkart, Swiggy, PhonePe, Razorpay, CRED, Atlassian |
Earn With It โ Interview Prep Coaching
๐ฐ Your Earning Path: Interview Prep Coaching (โน15Kโโน50K/month)
The Opportunity: India has 1.5 million+ engineering students preparing for placement each year. Most struggle with Dynamic Programming โ the #1 topic in coding interviews. If you master these 7 DP problems, you can coach juniors and earn โน15Kโโน50K/month.
How to Start Earning
Step 1: Build Your Proof (2โ4 weeks)
- Solve 50+ DP problems on LeetCode/CodeForces
- Get 4-star+ rating on CodeChef or 1600+ on Codeforces
- Create a blog/YouTube channel explaining DP problems
Step 2: Start Coaching (Month 1โ2)
- Offer 1-on-1 DP coaching to 3rd/4th year students at โน500โโน1,000/session
- Create a "DP Masterclass" (7 problems in 7 sessions) for โน3,000โโน5,000
- Post on LinkedIn: "Cracked FAANG DP problems. DM for coaching."
Step 3: Scale (Month 3+)
- Group sessions (5โ10 students): โน1,000โโน2,000/student for a 10-session course
- Create pre-recorded DP courses on Unacademy/YouTube: passive income
- Conduct mock interviews: โน500โโน1,500 per session
| Service | Rate | Monthly Potential |
|---|---|---|
| 1-on-1 DP Coaching | โน800โโน1,500/session | โน8Kโโน15K (10โ12 sessions/month) |
| Group DP Masterclass | โน3,000โโน5,000/student (7 sessions) | โน15Kโโน25K (5 students) |
| Mock Interviews | โน500โโน1,500/session | โน5Kโโน15K (10 sessions) |
| YouTube/Blog Ads | Varies | โน2Kโโน10K (once established) |
Chapter Summary โ DP Problem Classification
๐ What You've Mastered
In this unit, you've learned 7 classic Dynamic Programming problems, each with complete recurrences, table traces, dual-language code, complexity analysis, and space optimizations. These 7 problems form the backbone of 80%+ of DP interview questions at top tech companies.
DP Problem Classification Table
| Problem | Type | State | Time | Space | Key Technique |
|---|---|---|---|---|---|
| Binomial Coefficient | Combinatorial | dp[i][j] | O(nk) | O(k) | Pascal's Triangle, 1D optimization |
| Box Stacking | LIS Variant | dp[i] | O(nยฒ) | O(n) | Rotation generation, sort by base area |
| 0/1 Knapsack | Subset Selection | dp[i][w] | O(nW) | O(W) | Include/Exclude, right-to-left 1D |
| Edit Distance | String DP | dp[i][j] | O(mn) | O(mn) | 3-way min (insert/delete/replace) |
| LIS | Subsequence | dp[i] | O(nยฒ) / O(n log n) | O(n) | Binary search for O(n log n) |
| LCS | String DP | dp[i][j] | O(mn) | O(mn) | Match/skip, backtracking for string |
| Balanced Partition | Subset Sum | dp[j] bool | O(nS) | O(S) | Reduce to subset sum with target S/2 |
The DP Problem-Solving Framework
๐ง 5-Step DP Recipe (Works for ANY DP Problem)
Step 1 โ Define the State: What does dp[i] or dp[i][j] represent? This is the hardest step.
Step 2 โ Write the Recurrence: Express dp[i][j] in terms of smaller subproblems.
Step 3 โ Define Base Cases: What are the trivially solvable cases?
Step 4 โ Determine Fill Order: Bottom-up (small โ large) or top-down (memoization)?
Step 5 โ Extract the Answer: Where in the table is the final answer? Do you need backtracking?
Connections Between Problems
| Connection | Insight |
|---|---|
| Balanced Partition โ Knapsack | Partition is Knapsack with capacity = S/2 and value = weight |
| Box Stacking โ LIS | Stacking is 2D LIS after rotation generation |
| Longest Palindromic Subsequence โ LCS | LPS(S) = LCS(S, reverse(S)) |
| LCS โ Edit Distance | For insert/delete only: EditDist = m + n โ 2รLCS |
| Coin Change โ Unbounded Knapsack | Same DP but items can be reused |
Earning Checkpoint โ Self-Assessment
| Skill | Tool / Technique | Portfolio Deliverable | Earn-Ready? |
|---|---|---|---|
| Binomial Coefficient | Pascal's Triangle DP | Working C++/Python code | โ Yes โ foundation for combinatorics questions |
| Box Stacking | LIS variant, rotation generation | Full solution with trace | โ Yes โ logistics optimization interviews |
| 0/1 Knapsack | 2D DP + 1D optimization | Solution with backtracking | โ Yes โ appears in 30%+ coding interviews |
| Edit Distance | String DP, 3-way recurrence | Full solution + operations list | โ Yes โ NLP/bioinformatics interviews |
| LIS | O(nยฒ) DP + O(n log n) binary search | Both approaches implemented | โ Yes โ core competitive programming skill |
| LCS | String DP + backtracking | Solution with actual subsequence | โ Yes โ diff tools, DNA matching |
| Balanced Partition | Subset Sum reduction | Working solution | โ Yes โ resource allocation problems |
| Interview Coaching | Teaching DP to juniors | 7-problem masterclass curriculum | โ Yes โ โน15Kโโน50K/month potential |
๐ฏ Post-Unit Action Items
- โ Solve all 7 problems from memory (no reference)
- โ Complete all 5 tracing exercises (Section E)
- โ Solve at least 5 programming problems from the Problem Set
- โ Score 24/30+ on the MCQ assessment
- โ Solve 20+ DP problems on LeetCode (EasyโMedium)
- โ Create your "DP Masterclass" outline for coaching
- โ Apply to at least 3 companies with DP in their interview process
โ Unit 5 complete. You now think in states and transitions. Ready for Unit 6!
[QR: Link to EduArtha video tutorial โ Dynamic Programming Masterclass]