💡 Memoization caches results of recursive calls to avoid recomputation, drastically improving efficiency.
Intuition
Store results of subproblems (prefix pairs) so each is computed once, turning exponential recursion into polynomial time.
Algorithm
- Initialize a memo dictionary to store results for (i, j) pairs.
- If either string prefix is empty, return the other prefix.
- If last chars match, recurse on smaller prefixes and append the char.
- Else, recurse on both options, store and return the shorter supersequence.
💡 Memoization avoids repeated work by caching results, making recursion feasible for larger inputs.
Recurrence:Same as brute force: scs(i,j) = if chars equal: 1 + scs(i-1,j-1) else 1 + min(scs(i-1,j), scs(i,j-1))
def scs(str1, str2):
memo = {}
def dfs(i, j):
if (i, j) in memo:
return memo[(i, j)]
if i == 0:
memo[(i, j)] = str2[:j]
return memo[(i, j)]
if j == 0:
memo[(i, j)] = str1[:i]
return memo[(i, j)]
if str1[i-1] == str2[j-1]:
memo[(i, j)] = dfs(i-1, j-1) + str1[i-1]
else:
left = dfs(i-1, j) + str1[i-1]
right = dfs(i, j-1) + str2[j-1]
memo[(i, j)] = left if len(left) < len(right) else right
return memo[(i, j)]
return dfs(len(str1), len(str2))
if __name__ == '__main__':
print(scs("abac", "cab"))
Line Notes
memo = {}Initialize cache to store results for subproblems
if (i, j) in memo:Return cached result if available to avoid recomputation
memo[(i, j)] = dfs(i-1, j-1) + str1[i-1]Store result when last chars match
memo[(i, j)] = left if len(left) < len(right) else rightStore shorter supersequence result for mismatched chars
import java.util.*;
public class Solution {
Map<String, String> memo = new HashMap<>();
public String shortestCommonSupersequence(String str1, String str2) {
return dfs(str1, str2, str1.length(), str2.length());
}
private String dfs(String s1, String s2, int i, int j) {
String key = i + "," + j;
if (memo.containsKey(key)) return memo.get(key);
if (i == 0) {
memo.put(key, s2.substring(0, j));
return memo.get(key);
}
if (j == 0) {
memo.put(key, s1.substring(0, i));
return memo.get(key);
}
if (s1.charAt(i - 1) == s2.charAt(j - 1)) {
memo.put(key, dfs(s1, s2, i - 1, j - 1) + s1.charAt(i - 1));
} else {
String left = dfs(s1, s2, i - 1, j) + s1.charAt(i - 1);
String right = dfs(s1, s2, i, j - 1) + s2.charAt(j - 1);
memo.put(key, left.length() < right.length() ? left : right);
}
return memo.get(key);
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.shortestCommonSupersequence("abac", "cab"));
}
}
Line Notes
Map<String, String> memo = new HashMap<>();Cache results for subproblems using string keys
String key = i + "," + j;Create unique key for current subproblem
if (memo.containsKey(key)) return memo.get(key);Return cached result if present
memo.put(key, left.length() < right.length() ? left : right);Store shorter supersequence result
#include <iostream>
#include <string>
#include <unordered_map>
using namespace std;
unordered_map<string, string> memo;
string dfs(const string& s1, const string& s2, int i, int j) {
string key = to_string(i) + "," + to_string(j);
if (memo.count(key)) return memo[key];
if (i == 0) return memo[key] = s2.substr(0, j);
if (j == 0) return memo[key] = s1.substr(0, i);
if (s1[i - 1] == s2[j - 1]) {
return memo[key] = dfs(s1, s2, i - 1, j - 1) + s1[i - 1];
} else {
string left = dfs(s1, s2, i - 1, j) + s1[i - 1];
string right = dfs(s1, s2, i, j - 1) + s2[j - 1];
return memo[key] = (left.size() < right.size()) ? left : right;
}
}
int main() {
cout << dfs("abac", "cab", 4, 3) << endl;
return 0;
}
Line Notes
unordered_map<string, string> memo;Cache subproblem results with string keys
string key = to_string(i) + "," + to_string(j);Unique key for current subproblem
if (memo.count(key)) return memo[key];Return cached result if exists
return memo[key] = (left.size() < right.size()) ? left : right;Store and return shorter supersequence
function scs(str1, str2) {
const memo = new Map();
function dfs(i, j) {
const key = i + ',' + j;
if (memo.has(key)) return memo.get(key);
if (i === 0) {
memo.set(key, str2.slice(0, j));
return memo.get(key);
}
if (j === 0) {
memo.set(key, str1.slice(0, i));
return memo.get(key);
}
if (str1[i - 1] === str2[j - 1]) {
memo.set(key, dfs(i - 1, j - 1) + str1[i - 1]);
} else {
const left = dfs(i - 1, j) + str1[i - 1];
const right = dfs(i, j - 1) + str2[j - 1];
memo.set(key, left.length < right.length ? left : right);
}
return memo.get(key);
}
return dfs(str1.length, str2.length);
}
console.log(scs("abac", "cab"));
Line Notes
const memo = new Map();Cache results for subproblems
const key = i + ',' + j;Unique key for memoization
if (memo.has(key)) return memo.get(key);Return cached result if available
memo.set(key, left.length < right.length ? left : right);Store shorter supersequence result
TimeO(m*n * (m+n))
SpaceO(m*n * (m+n)) due to memo storing strings
Each subproblem computed once, but concatenation of strings costs extra time and space.
💡 Memoization reduces calls drastically but string concatenation can still be costly.
Interview Verdict: Accepted but can be optimized
Memoization makes solution feasible but not optimal due to string concatenation overhead.