πΉ Approach (2 Steps)
β Step 1: Build DP Table
- Same as LCS length
β Step 2: Backtrack
- Start from
(n, m) -
Move:
- Diagonal β if characters match (take char)
- Up / Left β based on max value
πΉ Full Code (C++)
string longestCommonSubsequence(string s1, string s2) {
int n = s1.size(), m = s2.size();
// Step 1: DP table
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++){
if(s1[i-1] == s2[j-1])
dp[i][j] = 1 + dp[i-1][j-1];
else
dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
}
}
// Step 2: Backtracking
int i = n, j = m;
string ans = "";
while(i > 0 && j > 0){
if(s1[i-1] == s2[j-1]){
ans.push_back(s1[i-1]);
i--;
j--;
}
else if(dp[i-1][j] > dp[i][j-1]){
i--; // move up
}
else{
j--; // move left
}
}
// reverse because we built it backwards
reverse(ans.begin(), ans.end());
return ans;
}
πΉ Example
π Input:
s1 = "AGGTAB"
s2 = "GXTXAYB"
π Output:
GTAB
πΉ Key Intuition π§
- Matching characters β part of LCS
- Otherwise β follow the path that gave max value
π Rule:
Match β take & go diagonal
Not match β go where value is larger
πΉ Complexity
- Time: O(n Γ m)
- Space: O(n Γ m)
πΉ Important Notes β οΈ
- Always reverse the answer
- This gives one valid LCS, not necessarily all
- Works for strings, arrays, etc.
π₯ Interview Tip
If asked:
βPrint LCSβ
Say confidently:
- Build DP table
- Backtrack from
(n, m)
π Hirschbergβs Algorithm (LCS with O(m) space)
πΉ Why do we need it?
Normal LCS:
- Time:
O(n*m) - Space:
O(n*m)β (too large for big strings)
π Hirschberg reduces:
- Space β O(m) β
- Still prints LCS!
π₯ Core Idea (Divide & Conquer)
Instead of storing full DP table:
π Split string s1 into two halves:
s1 = left_half + right_half
Then:
- Compute LCS length of
left_halfwith all prefixes ofs2 - Compute LCS length of
right_half(reversed) with all suffixes ofs2 - Find best split point
kins2 - Recursively solve:
(left_half, s2[0..k])(right_half, s2[k..m])
π§ Visualization
s1 = ABC | DEF
s2 = GHIJKL
β Find split k in s2
β Solve:
LCS(ABC, s2[0..k])
+
LCS(DEF, s2[k..end])
πΉ Key Trick
We only compute one row at a time β saves space
πΉ Helper Function (1D DP)
vector<int> lcsLength(string a, string b) {
int n = a.size(), m = b.size();
vector<int> prev(m+1, 0), curr(m+1, 0);
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
if(a[i-1] == b[j-1])
curr[j] = 1 + prev[j-1];
else
curr[j] = max(prev[j], curr[j-1]);
}
prev = curr;
}
return prev;
}
πΉ Main Hirschberg Function
string hirschberg(string s1, string s2) {
int n = s1.size(), m = s2.size();
// base cases
if(n == 0) return "";
if(n == 1) {
for(char c : s2) {
if(c == s1[0]) return string(1, c);
}
return "";
}
int mid = n / 2;
string left = s1.substr(0, mid);
string right = s1.substr(mid);
// forward LCS
vector<int> L1 = lcsLength(left, s2);
// reverse LCS
reverse(right.begin(), right.end());
string rev_s2 = s2;
reverse(rev_s2.begin(), rev_s2.end());
vector<int> L2 = lcsLength(right, rev_s2);
// find partition k
int k = 0, maxVal = 0;
for(int j = 0; j <= m; j++){
int val = L1[j] + L2[m - j];
if(val > maxVal){
maxVal = val;
k = j;
}
}
// divide and conquer
string lcs_left = hirschberg(left, s2.substr(0, k));
string lcs_right = hirschberg(s1.substr(mid), s2.substr(k));
return lcs_left + lcs_right;
}
πΉ Complexity
| Metric | Value |
|---|---|
| Time | O(n Γ m) |
| Space | O(m) β |
π₯ Why it Works
- Avoids storing full DP table
- Uses only two rows at a time
- Reconstructs answer via recursion
β οΈ When to Use
π Use Hirschberg when:
- Strings are very large
- Memory is limited
- Interviewer asks βoptimize space for printing LCSβ
π§ Interview Answer (Perfect π₯)
If asked:
βCan you print LCS in O(m) space?β
Say:
βYes, using Hirschbergβs Algorithm β a divide & conquer approach that computes LCS using 1D DP and reconstructs the sequence recursively.β
β‘ Final Intuition
Split β Solve β Combine
Use forward + reverse DP to find split point
If you want π next:
- Step-by-step dry run of Hirschberg
- Compare with normal DP visually
- Convert this into interview-ready explanation
Just tell me π
Top comments (0)