mirror of
https://github.com/TheAlgorithms/JavaScript.git
synced 2025-07-05 08:16:50 +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.
|
Problem:
|
||||||
* A subsequence is a sequence that appears in the same relative order, but not necessarily contiguous.
|
Given two sequences, find the length of longest subsequence present in both of them.
|
||||||
* For example, “abc”, “abg”, “bdf”, “aeg”, ‘”acefg”, .. etc are subsequences of “abcdefg”
|
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) {
|
* Finds length of the longest common subsequence among the two input string
|
||||||
return 0
|
* @param {string} str1 Input string #1
|
||||||
} else {
|
* @param {string} str2 Input string #2
|
||||||
if (dp[x][y] !== 0) {
|
* @returns {number} Length of the longest common subsequence
|
||||||
return dp[x][y]
|
*/
|
||||||
|
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 {
|
} else {
|
||||||
if (str1[x] === str2[y]) {
|
memo[end1][end2] = Math.max(
|
||||||
dp[x][y] = 1 + longestCommonSubsequence(x - 1, y - 1, str1, str2, dp)
|
recursive(end1 - 1, end2),
|
||||||
return dp[x][y]
|
recursive(end1, end2 - 1)
|
||||||
} else {
|
)
|
||||||
dp[x][y] = Math.max(longestCommonSubsequence(x - 1, y, str1, str2, dp), longestCommonSubsequence(x, y - 1, str1, str2, dp))
|
return memo[end1][end2]
|
||||||
return dp[x][y]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 }
|
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