Skip to content

Commit 7560beb

Browse files
authored
merge: Add tests and docs for the Longest Common Subsequence algorithm (TheAlgorithms#867)
* Refactors, adds tests and comments to longest common subsequence algorithm * Refactor docs for longest common subsequence algorithm * Add links to wikipedia and leetcode * Fix styling * Refactor variable naming and jsdoc
1 parent 43515a6 commit 7560beb

File tree

2 files changed

+81
-23
lines changed

2 files changed

+81
-23
lines changed
Lines changed: 49 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,58 @@
11
/*
2-
* Given two sequences, find the length of longest subsequence present in both of them.
3-
* A subsequence is a sequence that appears in the same relative order, but not necessarily contiguous.
4-
* For example, “abc”, “abg”, “bdf”, “aeg”, ‘”acefg”, .. etc are subsequences of “abcdefg”
2+
Problem:
3+
Given two sequences, find the length of longest subsequence present in both of them.
4+
A subsequence is a sequence that appears in the same relative order, but not necessarily contiguous.
5+
For example, “abc”, “abg”, “bdf”, “aeg”, ‘”acefg”, .. etc are subsequences of “abcdefg”
6+
7+
Our Solution:
8+
We use recursion with tabular memoization.
9+
Time complexity: O(M x N)
10+
Solving each subproblem has a cost of O(1). Again, there are MxN subproblems,
11+
and so we get a total time complexity of O(MxN).
12+
Space complexity: O(M x N)
13+
We need to store the answer for each of the MxN subproblems.
14+
15+
Improvement:
16+
It's possible to optimize space complexity to O(min(M, N)) or time to O((N + r)log(N))
17+
where r is the number of matches between the two sequences. Try to figure out how.
18+
19+
References:
20+
[wikipedia](https://en.wikipedia.org/wiki/Longest_common_subsequence_problem)
21+
[leetcode](https://leetcode.com/problems/longest-common-subsequence/)
522
*/
623

7-
function longestCommonSubsequence (x, y, str1, str2, dp) {
8-
if (x === -1 || y === -1) {
9-
return 0
10-
} else {
11-
if (dp[x][y] !== 0) {
12-
return dp[x][y]
24+
/**
25+
* Finds length of the longest common subsequence among the two input string
26+
* @param {string} str1 Input string #1
27+
* @param {string} str2 Input string #2
28+
* @returns {number} Length of the longest common subsequence
29+
*/
30+
function longestCommonSubsequence (str1, str2) {
31+
const memo = new Array(str1.length + 1).fill(null)
32+
.map(() => new Array(str2.length + 1).fill(null))
33+
34+
function recursive (end1, end2) {
35+
if (end1 === -1 || end2 === -1) {
36+
return 0
37+
}
38+
39+
if (memo[end1][end2] !== null) {
40+
return memo[end1][end2]
41+
}
42+
43+
if (str1[end1] === str2[end2]) {
44+
memo[end1][end2] = 1 + recursive(end1 - 1, end2 - 1)
45+
return memo[end1][end2]
1346
} else {
14-
if (str1[x] === str2[y]) {
15-
dp[x][y] = 1 + longestCommonSubsequence(x - 1, y - 1, str1, str2, dp)
16-
return dp[x][y]
17-
} else {
18-
dp[x][y] = Math.max(longestCommonSubsequence(x - 1, y, str1, str2, dp), longestCommonSubsequence(x, y - 1, str1, str2, dp))
19-
return dp[x][y]
20-
}
47+
memo[end1][end2] = Math.max(
48+
recursive(end1 - 1, end2),
49+
recursive(end1, end2 - 1)
50+
)
51+
return memo[end1][end2]
2152
}
2253
}
23-
}
2454

25-
// Example
26-
27-
// const str1 = 'ABCDGH'
28-
// const str2 = 'AEDFHR'
29-
// const dp = new Array(str1.length + 1).fill(0).map(x => new Array(str2.length + 1).fill(0))
30-
// const res = longestCommonSubsequence(str1.length - 1, str2.length - 1, str1, str2, dp)
55+
return recursive(str1.length - 1, str2.length - 1)
56+
}
3157

3258
export { longestCommonSubsequence }
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { longestCommonSubsequence } from '../LongestCommonSubsequence'
2+
3+
describe('LongestCommonSubsequence', () => {
4+
it('expects to return an empty string for empty inputs', () => {
5+
expect(longestCommonSubsequence('', '')).toEqual(''.length)
6+
expect(longestCommonSubsequence('aaa', '')).toEqual(''.length)
7+
expect(longestCommonSubsequence('', 'bbb')).toEqual(''.length)
8+
})
9+
10+
it('expects to return an empty string for inputs without a common subsequence', () => {
11+
expect(longestCommonSubsequence('abc', 'deffgf')).toEqual(''.length)
12+
expect(longestCommonSubsequence('de', 'ghm')).toEqual(''.length)
13+
expect(longestCommonSubsequence('aupj', 'xyz')).toEqual(''.length)
14+
})
15+
16+
it('expects to return the longest common subsequence, short inputs', () => {
17+
expect(longestCommonSubsequence('abc', 'abc')).toEqual('abc'.length)
18+
expect(longestCommonSubsequence('abc', 'abcd')).toEqual('abc'.length)
19+
expect(longestCommonSubsequence('abc', 'ab')).toEqual('ab'.length)
20+
expect(longestCommonSubsequence('abc', 'a')).toEqual('a'.length)
21+
expect(longestCommonSubsequence('abc', 'b')).toEqual('b'.length)
22+
expect(longestCommonSubsequence('abc', 'c')).toEqual('c'.length)
23+
expect(longestCommonSubsequence('abd', 'abcd')).toEqual('abd'.length)
24+
expect(longestCommonSubsequence('abd', 'ab')).toEqual('ab'.length)
25+
expect(longestCommonSubsequence('abc', 'abd')).toEqual('ab'.length)
26+
})
27+
28+
it('expects to return the longest common subsequence, medium-length inputs', () => {
29+
expect(longestCommonSubsequence('bsbininm', 'jmjkbkjkv')).toEqual('b'.length)
30+
expect(longestCommonSubsequence('oxcpqrsvwf', 'shmtulqrypy')).toEqual('qr'.length)
31+
})
32+
})

0 commit comments

Comments
 (0)