-
-
Notifications
You must be signed in to change notification settings - Fork 223
Expand file tree
/
Copy pathlongestCommonSubsequence.js
More file actions
119 lines (109 loc) · 2.97 KB
/
longestCommonSubsequence.js
File metadata and controls
119 lines (109 loc) · 2.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
/**
* TC:O(m * n) SC:O(m* n)
* Returns the length of the longest common subsequence between two strings using two dimensional bottom-up DP.
* @param {string} str1
* @param {string} str2
* @returns {number}
*/
function lengthOfLCS(str1, str2) {
const m = str1.length, n = str2.length;
const dp = Array(m + 1)
.fill()
.map(() => Array(n + 1).fill(0));
for (let i = m - 1; i >= 0; i--) {
for (let j = n - 1; j >= 0; j--) {
if (str1[i] === str2[j]) {
dp[i][j] = 1 + dp[i + 1][j + 1];
} else {
dp[i][j] = Math.max(dp[i][j + 1], dp[i + 1][j]);
}
}
}
return dp[0][0];
}
/**
* Returns the length of the longest common subsequence using recursion with memoization.
* @param {string} str1
* @param {string} str2
* @returns {number}
*/
function lengthOfLCSRecursive(str1, str2) {
const memo = {};
function helper(i, j) {
// Base case: if either string is fully traversed
if (i >= str1.length || j >= str2.length) return 0;
const key = `${i},${j}`;
if (memo[key] !== undefined) return memo[key];
if (str1[i] === str2[j]) {
memo[key] = 1 + helper(i + 1, j + 1);
} else {
memo[key] = Math.max(helper(i + 1, j), helper(i, j + 1));
}
return memo[key];
}
return helper(0, 0);
}
/**
* Returns the actual longest common subsequence string using bottom-up DP.
* @param {string} str1
* @param {string} str2
* @returns {string}
*/
function getLCS(str1, str2) {
const m = str1.length,
n = str2.length;
const dp = Array(m + 1)
.fill()
.map(() => Array(n + 1).fill(0));
for (let i = m - 1; i >= 0; i--) {
for (let j = n - 1; j >= 0; j--) {
if (str1[i] === str2[j]) {
dp[i][j] = 1 + dp[i + 1][j + 1];
} else {
dp[i][j] = Math.max(dp[i][j + 1], dp[i + 1][j]);
}
}
}
// Reconstruct LCS
let i = 0,
j = 0,
lcs = "";
while (i < m && j < n) {
if (str1[i] === str2[j]) {
lcs += str1[i];
i++;
j++;
} else if (dp[i][j + 1] > dp[i + 1][j]) {
j++;
} else {
i++;
}
}
return lcs;
}
// Test cases
const testCases = [
{ str1: "abcdef", str2: "acbefd", expectedLen: 4, expectedLCS: "abef" },
{ str1: "abcd", str2: "efgh", expectedLen: 0, expectedLCS: "" },
{ str1: "", str2: "abc", expectedLen: 0, expectedLCS: "" },
{ str1: "abc", str2: "", expectedLen: 0, expectedLCS: "" },
{ str1: "abc", str2: "abc", expectedLen: 3, expectedLCS: "abc" },
{ str1: "abcde", str2: "ace", expectedLen: 3, expectedLCS: "ace" },
];
for (const { str1, str2, expectedLen, expectedLCS } of testCases) {
console.log(
`str1 = "${str1}", str2 = "${str2}" | DP: ${lengthOfLCS(
str1,
str2
)} | Recursive: ${lengthOfLCSRecursive(str1, str2)} | LCS: "${getLCS(
str1,
str2
)}" | Expected Length: ${expectedLen} | Expected LCS: "${expectedLCS}"`
);
}
// Edge case: invalid input
try {
lengthOfLCS(123, "abc");
} catch (e) {
console.log("Error (as expected for invalid input):", e.message);
}