Dynamic Programming II: Classic Problems
DP — LIS, LCS, Knapsack, Edit Distance
1. The Five Classics
- LIS — Longest Increasing Subsequence
- LCS — Longest Common Subsequence
- Edit Distance — minimum edits to transform one string into another
- Knapsack — pack maximum value under weight limit
- Matrix Chain Multiplication — minimum scalar multiplications
Every interview, every contest — these five (or close cousins) appear constantly. Drilling them gives a vocabulary you can transfer to new DP problems instantly.
2. LIS in O(n log n)
Maintain tails: tails[k] = smallest possible tail of any increasing subsequence
of length k+1 seen so far. For each new x, binary-search for the first tails[i] ≥ x and
replace it. Length of tails at the end = LIS length.
#include <bits/stdc++.h>
using namespace std;
int LIS(vector<int>& a) {
vector<int> tails;
for (int x : a) {
auto it = lower_bound(tails.begin(), tails.end(), x);
if (it == tails.end()) tails.push_back(x);
else *it = x;
}
return tails.size();
}
int main() {
vector<int> a = {10, 9, 2, 5, 3, 7, 101, 18};
cout << "LIS = " << LIS(a); // 4 (e.g. 2,3,7,101)
}
3. LCS & Edit Distance
LCS: dp[i][j] = LCS length of a[0..i−1] and b[0..j−1]. If a[i−1] == b[j−1]: dp[i][j] = dp[i−1][j−1] + 1. Else dp[i][j] = max(dp[i−1][j], dp[i][j−1]). O(nm).
Edit Distance (Levenshtein): dp[i][j] = ops to convert a[0..i−1] into b[0..j−1]. Three operations (insert / delete / replace), each costs 1:
dp[i][j] = (a[i−1] == b[j−1]) ? dp[i−1][j−1] : 1 + min(dp[i−1][j], dp[i][j−1], dp[i−1][j−1])
#include <bits/stdc++.h>
using namespace std;
int LCS(const string& a, const string& b) {
int n = a.size(), m = b.size();
vector<vector<int>> dp(n+1, vector<int>(m+1, 0));
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
dp[i][j] = (a[i-1] == b[j-1]) ? dp[i-1][j-1] + 1
: max(dp[i-1][j], dp[i][j-1]);
return dp[n][m];
}
int edit(const string& a, const string& b) {
int n = a.size(), m = b.size();
vector<vector<int>> dp(n+1, vector<int>(m+1));
for (int i = 0; i <= n; i++) dp[i][0] = i;
for (int j = 0; j <= m; j++) dp[0][j] = j;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
dp[i][j] = (a[i-1] == b[j-1])
? dp[i-1][j-1]
: 1 + min({dp[i-1][j], dp[i][j-1], dp[i-1][j-1]});
return dp[n][m];
}
int main() {
cout << "LCS ABCBDAB / BDCAB = " << LCS("ABCBDAB", "BDCAB") << "\n";
cout << "edit kitten / sitting = " << edit("kitten", "sitting");
}
4. Knapsack
0/1 Knapsack: n items, each with weight w[i] and value v[i]; pick subset
with total weight ≤ W to maximise value. dp[i][w] = best value using first i items in
weight w. Either skip i or take i: dp[i][w] = max(dp[i−1][w], dp[i−1][w − w[i]] + v[i]).
O(nW). Space-optimised: 1D dp[w] updated in reverse order so we don't reuse the same item.
#include <bits/stdc++.h>
using namespace std;
int main() {
vector<int> w = {2, 3, 4, 5};
vector<int> v = {3, 4, 5, 6};
int W = 5;
vector<int> dp(W + 1, 0);
for (int i = 0; i < (int)w.size(); i++)
for (int j = W; j >= w[i]; j--)
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
cout << "max value = " << dp[W];
}
Unbounded: each item can be taken any number of times → loop j forward instead.
5. Matrix Chain Multiplication
Given matrix dimensions p[0..n], find optimal parenthesisation. dp[i][j] = min scalar
multiplications to multiply A[i..j]. dp[i][j] = min over k of dp[i][k] + dp[k+1][j] + p[i−1]·p[k]·p[j].
O(n³).
6. Practice Problems
-
Longest common substring (contiguous) — different from LCS.Longest common substring।
✨ Show Answer (উত্তর দেখুন)
Approach: dp[i][j] = if a[i−1]==b[j−1] then dp[i−1][j−1] + 1 else 0. Answer = max over all dp[i][j].
-
Coin change — minimum coins to sum to amount, given unlimited coin supply.Coin change minimum coins।
✨ Show Answer (উত্তর দেখুন)
Approach: dp[v] = min coins to form value v. dp[v] = min over coin c of dp[v − c] + 1. dp[0] = 0, others = INF. O(V·n).
-
Coin change — number of ways to form amount (order doesn't matter).Coin change — total ways।
✨ Show Answer (উত্তর দেখুন)
Approach: dp[v] = ways to make v. Outer loop over coins, inner loop v from coin to amount: dp[v] += dp[v − coin]. Order coins outside avoids counting permutations.
-
Subset sum — does any subset of nums sum to target?Subset sum।
✨ Show Answer (উত্তর দেখুন)
Approach: bool dp[w]; dp[0] = true; for each num x, for w from W downto x: dp[w] |= dp[w − x]. O(n·W).
-
Partition equal subset sum — can the array be split into two halves with equal sum?Equal subset sum।
✨ Show Answer (উত্তর দেখুন)
Approach: if total sum is odd, impossible. Otherwise check subset sum target = total/2.
-
Egg drop — min trials with k eggs and n floors.Egg drop।
✨ Show Answer (উত্তর দেখুন)
Approach: the smart trick: dp[t][k] = max floors we can certify in t trials with k eggs. dp[t][k] = dp[t−1][k−1] + dp[t−1][k] + 1. Find smallest t with dp[t][k] ≥ n.
-
Print the actual LCS (not just length).LCS string পুনরুদ্ধার।
✨ Show Answer (উত্তর দেখুন)
Approach: after the DP, walk from dp[n][m] back to dp[0][0]: if a[i−1] == b[j−1] take it and go to (i−1, j−1); else go to whichever neighbour matches dp[i][j].
-
Why is the inner loop of 0/1 knapsack iterated backward?0/1 knapsack-এ inner loop reverse কেন?
✨ Show Answer (উত্তর দেখুন)
Answer: reverse order ensures we use the previous row's
dp[w − w[i]], not the current row's — preventing reuse of item i. Forward order would model unbounded knapsack instead.
Summary — Module 35
LIS in O(n log n) via patience sorting. LCS / edit distance in O(nm) — common subsequence templates. 0/1 knapsack with reverse loop to avoid reuse. Matrix chain in O(n³). These five power 80% of all DP interview questions.