🧠
Top-Down DP with Memoization
💡 Memoization avoids recomputing the same subproblems, drastically improving efficiency over brute force.
Intuition
Store results of longest chains starting at each pair to reuse them instead of recomputing.
Algorithm
- Sort pairs by their first element.
- Use a recursive function with a memo dictionary to store results for each index.
- For each pair, recursively compute the longest chain starting there, caching results.
- Return the maximum chain length found.
💡 Memoization transforms exponential recursion into polynomial time by caching.
Recurrence:dp[i] = 1 + max(dp[j]) for all j > i where pairs[i][1] < pairs[j][0], else 1
from typing import List
class Solution:
def findLongestChain(self, pairs: List[List[int]]) -> int:
pairs.sort(key=lambda x: x[0])
n = len(pairs)
memo = {}
def dfs(i):
if i in memo:
return memo[i]
max_len = 1
for j in range(i + 1, n):
if pairs[i][1] < pairs[j][0]:
length = 1 + dfs(j)
if length > max_len:
max_len = length
memo[i] = max_len
return max_len
max_chain = 0
for i in range(n):
current = dfs(i)
if current > max_chain:
max_chain = current
return max_chain
# Driver code
if __name__ == '__main__':
sol = Solution()
print(sol.findLongestChain([[1,2],[2,3],[3,4]])) # Output: 2
Line Notes
memo = {}Initialize memo dictionary to cache results
if i in memo:Return cached result to avoid recomputation
memo[i] = max_lenStore computed result for index i
for j in range(i + 1, n):Check all pairs after i for chaining
if pairs[i][1] < pairs[j][0]:Check compatibility for chaining
max_chain = 0Track global maximum chain length
return max_chainReturn longest chain length found
import java.util.*;
public class Solution {
public int findLongestChain(int[][] pairs) {
Arrays.sort(pairs, Comparator.comparingInt(a -> a[0]));
int n = pairs.length;
Integer[] memo = new Integer[n];
int maxChain = 0;
for (int i = 0; i < n; i++) {
maxChain = Math.max(maxChain, dfs(pairs, i, memo));
}
return maxChain;
}
private int dfs(int[][] pairs, int i, Integer[] memo) {
if (memo[i] != null) return memo[i];
int maxLen = 1;
for (int j = i + 1; j < pairs.length; j++) {
if (pairs[i][1] < pairs[j][0]) {
int length = 1 + dfs(pairs, j, memo);
if (length > maxLen) {
maxLen = length;
}
}
}
memo[i] = maxLen;
return maxLen;
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.findLongestChain(new int[][]{{1,2},{2,3},{3,4}})); // Output: 2
}
}
Line Notes
Integer[] memo = new Integer[n]Memo array to cache results for each index
if (memo[i] != null) return memo[i]Return cached result if available
memo[i] = maxLenStore computed longest chain length for i
for (int j = i + 1; j < pairs.length; j++)Check all subsequent pairs for chaining
if (pairs[i][1] < pairs[j][0])Check if pairs are compatible
maxChain = Math.max(maxChain, dfs(pairs, i, memo))Update global maximum chain length
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Solution {
public:
int findLongestChain(vector<vector<int>>& pairs) {
sort(pairs.begin(), pairs.end(), [](auto &a, auto &b) { return a[0] < b[0]; });
int n = pairs.size();
vector<int> memo(n, -1);
int maxChain = 0;
for (int i = 0; i < n; i++) {
maxChain = max(maxChain, dfs(pairs, i, memo));
}
return maxChain;
}
private:
int dfs(vector<vector<int>>& pairs, int i, vector<int>& memo) {
if (memo[i] != -1) return memo[i];
int maxLen = 1;
for (int j = i + 1; j < pairs.size(); j++) {
if (pairs[i][1] < pairs[j][0]) {
int length = 1 + dfs(pairs, j, memo);
if (length > maxLen) {
maxLen = length;
}
}
}
memo[i] = maxLen;
return maxLen;
}
};
int main() {
Solution sol;
vector<vector<int>> pairs = {{1,2},{2,3},{3,4}};
cout << sol.findLongestChain(pairs) << endl; // Output: 2
return 0;
}
Line Notes
vector<int> memo(n, -1)Initialize memo vector with -1 to indicate uncomputed states
if (memo[i] != -1) return memo[i]Return cached result if computed
memo[i] = maxLenCache computed longest chain length for i
for (int j = i + 1; j < pairs.size(); j++)Check all subsequent pairs for chaining
if (pairs[i][1] < pairs[j][0])Check compatibility of pairs
maxChain = max(maxChain, dfs(pairs, i, memo))Update global maximum chain length
var findLongestChain = function(pairs) {
pairs.sort((a, b) => a[0] - b[0]);
const n = pairs.length;
const memo = new Array(n).fill(-1);
function dfs(i) {
if (memo[i] !== -1) return memo[i];
let maxLen = 1;
for (let j = i + 1; j < n; j++) {
if (pairs[i][1] < pairs[j][0]) {
const length = 1 + dfs(j);
if (length > maxLen) {
maxLen = length;
}
}
}
memo[i] = maxLen;
return maxLen;
}
let maxChain = 0;
for (let i = 0; i < n; i++) {
maxChain = Math.max(maxChain, dfs(i));
}
return maxChain;
};
// Test
console.log(findLongestChain([[1,2],[2,3],[3,4]])); // Output: 2
Line Notes
const memo = new Array(n).fill(-1)Initialize memo array with -1 for uncomputed states
if (memo[i] !== -1) return memo[i]Return cached result if available
memo[i] = maxLenCache computed longest chain length for i
for (let j = i + 1; j < n; j++)Check all subsequent pairs for chaining
if (pairs[i][1] < pairs[j][0])Check if pairs are compatible
maxChain = Math.max(maxChain, dfs(i))Update global maximum chain length
TimeO(n^2)
SpaceO(n) for memo and recursion stack
Memoization avoids repeated work but still checks pairs in nested loops, leading to O(n^2) time.
💡 For n=1000, this means about 1,000,000 operations, which is feasible but slow for very large inputs.
Interview Verdict: Accepted
This approach is efficient enough for moderate input sizes and demonstrates DP optimization.