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:
Leo Toff
2021-12-08 03:50:23 -08:00
committed by GitHub
parent 43515a646b
commit 7560beb068
2 changed files with 81 additions and 23 deletions

View File

@ -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) {
/**
* 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
} else {
if (dp[x][y] !== 0) {
return dp[x][y]
} 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]
}
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 {
memo[end1][end2] = Math.max(
recursive(end1 - 1, end2),
recursive(end1, end2 - 1)
)
return memo[end1][end2]
}
}
// 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)
return recursive(str1.length - 1, str2.length - 1)
}
export { longestCommonSubsequence }

View 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)
})
})