mirror of
https://github.com/TheAlgorithms/JavaScript.git
synced 2025-07-05 00:01:37 +08:00
merge: Add tests and docs for the Longest Common Subsequence algorithm (#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
This commit is contained in:
@ -1,32 +1,58 @@
|
||||
/*
|
||||
* Given two sequences, find the length of longest subsequence present in both of them.
|
||||
* A subsequence is a sequence that appears in the same relative order, but not necessarily contiguous.
|
||||
* For example, “abc”, “abg”, “bdf”, “aeg”, ‘”acefg”, .. etc are subsequences of “abcdefg”
|
||||
Problem:
|
||||
Given two sequences, find the length of longest subsequence present in both of them.
|
||||
A subsequence is a sequence that appears in the same relative order, but not necessarily contiguous.
|
||||
For example, “abc”, “abg”, “bdf”, “aeg”, ‘”acefg”, .. etc are subsequences of “abcdefg”
|
||||
|
||||
Our Solution:
|
||||
We use recursion with tabular memoization.
|
||||
Time complexity: O(M x N)
|
||||
Solving each subproblem has a cost of O(1). Again, there are MxN subproblems,
|
||||
and so we get a total time complexity of O(MxN).
|
||||
Space complexity: O(M x N)
|
||||
We need to store the answer for each of the MxN subproblems.
|
||||
|
||||
Improvement:
|
||||
It's possible to optimize space complexity to O(min(M, N)) or time to O((N + r)log(N))
|
||||
where r is the number of matches between the two sequences. Try to figure out how.
|
||||
|
||||
References:
|
||||
[wikipedia](https://en.wikipedia.org/wiki/Longest_common_subsequence_problem)
|
||||
[leetcode](https://leetcode.com/problems/longest-common-subsequence/)
|
||||
*/
|
||||
|
||||
function longestCommonSubsequence (x, y, str1, str2, dp) {
|
||||
if (x === -1 || y === -1) {
|
||||
return 0
|
||||
} else {
|
||||
if (dp[x][y] !== 0) {
|
||||
return dp[x][y]
|
||||
/**
|
||||
* Finds length of the longest common subsequence among the two input string
|
||||
* @param {string} str1 Input string #1
|
||||
* @param {string} str2 Input string #2
|
||||
* @returns {number} Length of the longest common subsequence
|
||||
*/
|
||||
function longestCommonSubsequence (str1, str2) {
|
||||
const memo = new Array(str1.length + 1).fill(null)
|
||||
.map(() => new Array(str2.length + 1).fill(null))
|
||||
|
||||
function recursive (end1, end2) {
|
||||
if (end1 === -1 || end2 === -1) {
|
||||
return 0
|
||||
}
|
||||
|
||||
if (memo[end1][end2] !== null) {
|
||||
return memo[end1][end2]
|
||||
}
|
||||
|
||||
if (str1[end1] === str2[end2]) {
|
||||
memo[end1][end2] = 1 + recursive(end1 - 1, end2 - 1)
|
||||
return memo[end1][end2]
|
||||
} else {
|
||||
if (str1[x] === str2[y]) {
|
||||
dp[x][y] = 1 + longestCommonSubsequence(x - 1, y - 1, str1, str2, dp)
|
||||
return dp[x][y]
|
||||
} else {
|
||||
dp[x][y] = Math.max(longestCommonSubsequence(x - 1, y, str1, str2, dp), longestCommonSubsequence(x, y - 1, str1, str2, dp))
|
||||
return dp[x][y]
|
||||
}
|
||||
memo[end1][end2] = Math.max(
|
||||
recursive(end1 - 1, end2),
|
||||
recursive(end1, end2 - 1)
|
||||
)
|
||||
return memo[end1][end2]
|
||||
}
|
||||
}
|
||||
|
||||
return recursive(str1.length - 1, str2.length - 1)
|
||||
}
|
||||
|
||||
// Example
|
||||
|
||||
// const str1 = 'ABCDGH'
|
||||
// const str2 = 'AEDFHR'
|
||||
// const dp = new Array(str1.length + 1).fill(0).map(x => new Array(str2.length + 1).fill(0))
|
||||
// const res = longestCommonSubsequence(str1.length - 1, str2.length - 1, str1, str2, dp)
|
||||
|
||||
export { longestCommonSubsequence }
|
||||
|
32
Dynamic-Programming/tests/LongestCommonSubsequence.test.js
Normal file
32
Dynamic-Programming/tests/LongestCommonSubsequence.test.js
Normal file
@ -0,0 +1,32 @@
|
||||
import { longestCommonSubsequence } from '../LongestCommonSubsequence'
|
||||
|
||||
describe('LongestCommonSubsequence', () => {
|
||||
it('expects to return an empty string for empty inputs', () => {
|
||||
expect(longestCommonSubsequence('', '')).toEqual(''.length)
|
||||
expect(longestCommonSubsequence('aaa', '')).toEqual(''.length)
|
||||
expect(longestCommonSubsequence('', 'bbb')).toEqual(''.length)
|
||||
})
|
||||
|
||||
it('expects to return an empty string for inputs without a common subsequence', () => {
|
||||
expect(longestCommonSubsequence('abc', 'deffgf')).toEqual(''.length)
|
||||
expect(longestCommonSubsequence('de', 'ghm')).toEqual(''.length)
|
||||
expect(longestCommonSubsequence('aupj', 'xyz')).toEqual(''.length)
|
||||
})
|
||||
|
||||
it('expects to return the longest common subsequence, short inputs', () => {
|
||||
expect(longestCommonSubsequence('abc', 'abc')).toEqual('abc'.length)
|
||||
expect(longestCommonSubsequence('abc', 'abcd')).toEqual('abc'.length)
|
||||
expect(longestCommonSubsequence('abc', 'ab')).toEqual('ab'.length)
|
||||
expect(longestCommonSubsequence('abc', 'a')).toEqual('a'.length)
|
||||
expect(longestCommonSubsequence('abc', 'b')).toEqual('b'.length)
|
||||
expect(longestCommonSubsequence('abc', 'c')).toEqual('c'.length)
|
||||
expect(longestCommonSubsequence('abd', 'abcd')).toEqual('abd'.length)
|
||||
expect(longestCommonSubsequence('abd', 'ab')).toEqual('ab'.length)
|
||||
expect(longestCommonSubsequence('abc', 'abd')).toEqual('ab'.length)
|
||||
})
|
||||
|
||||
it('expects to return the longest common subsequence, medium-length inputs', () => {
|
||||
expect(longestCommonSubsequence('bsbininm', 'jmjkbkjkv')).toEqual('b'.length)
|
||||
expect(longestCommonSubsequence('oxcpqrsvwf', 'shmtulqrypy')).toEqual('qr'.length)
|
||||
})
|
||||
})
|
Reference in New Issue
Block a user