diff --git a/README.md b/README.md index c90156ae..73f57b06 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ * [Fisher–Yates Shuffle](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sets/fisher-yates) - random permutation of a finite sequence * [Longest Common Subsequence](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sets/longest-common-subsequnce) (LCS) * [Longest Increasing subsequence](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sets/longest-increasing-subsequence) + * [Shortest Common Supersequence](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sets/shortest-common-supersequence) (SCS) * **String** * [Levenshtein Distance](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/string/levenshtein-distance) - minimum edit distance between two sequences * [Hamming Distance](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/string/hamming-distance) - number of positions at which the symbols are different @@ -92,7 +93,7 @@ * [Longest Common Subsequence](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sets/longest-common-subsequnce) (LCS) * [Longest Common Substring](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/string/longest-common-substring) * [Longest Increasing subsequence](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sets/longest-increasing-subsequence) - * Shortest common supersequence + * [Shortest Common Supersequence](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sets/shortest-common-supersequence) * Knapsack problem * Maximum subarray * Maximum sum path diff --git a/src/algorithms/sets/shortest-common-supersequence/README.md b/src/algorithms/sets/shortest-common-supersequence/README.md new file mode 100644 index 00000000..5af90a83 --- /dev/null +++ b/src/algorithms/sets/shortest-common-supersequence/README.md @@ -0,0 +1,24 @@ +# Shortest Common Supersequence + +The shortest common supersequence (SCS) of two sequences `X` and `Y` +is the shortest sequence which has `X` and `Y` as subsequences. + +In other words assume we're given two strings str1 and str2, find +the shortest string that has both str1 and str2 as subsequences. + +This is a problem closely related to the longest common +subsequence problem. + +## Example + +``` +Input: str1 = "geek", str2 = "eke" +Output: "geeke" + +Input: str1 = "AGGTAB", str2 = "GXTXAYB" +Output: "AGXGTXAYB" +``` + +## References + +- [GeeksForGeeks](https://www.geeksforgeeks.org/shortest-common-supersequence/) diff --git a/src/algorithms/sets/shortest-common-supersequence/__test__/shortestCommonSupersequence.test.js b/src/algorithms/sets/shortest-common-supersequence/__test__/shortestCommonSupersequence.test.js new file mode 100644 index 00000000..8adf27d1 --- /dev/null +++ b/src/algorithms/sets/shortest-common-supersequence/__test__/shortestCommonSupersequence.test.js @@ -0,0 +1,35 @@ +import shortestCommonSupersequence from '../shortestCommonSupersequence'; + +describe('shortestCommonSupersequence', () => { + it('should find shortest common supersequence of two sequences', () => { + // LCS (longest common subsequence) is empty + expect(shortestCommonSupersequence( + ['A', 'B', 'C'], + ['D', 'E', 'F'], + )).toEqual(['A', 'B', 'C', 'D', 'E', 'F']); + + // LCS (longest common subsequence) is "EE" + expect(shortestCommonSupersequence( + ['G', 'E', 'E', 'K'], + ['E', 'K', 'E'], + )).toEqual(['G', 'E', 'K', 'E', 'K']); + + // LCS (longest common subsequence) is "GTAB" + expect(shortestCommonSupersequence( + ['A', 'G', 'G', 'T', 'A', 'B'], + ['G', 'X', 'T', 'X', 'A', 'Y', 'B'], + )).toEqual(['A', 'G', 'G', 'X', 'T', 'X', 'A', 'Y', 'B']); + + // LCS (longest common subsequence) is "BCBA". + expect(shortestCommonSupersequence( + ['A', 'B', 'C', 'B', 'D', 'A', 'B'], + ['B', 'D', 'C', 'A', 'B', 'A'], + )).toEqual(['A', 'B', 'D', 'C', 'A', 'B', 'D', 'A', 'B']); + + // LCS (longest common subsequence) is "BDABA". + expect(shortestCommonSupersequence( + ['B', 'D', 'C', 'A', 'B', 'A'], + ['A', 'B', 'C', 'B', 'D', 'A', 'B', 'A', 'C'], + )).toEqual(['A', 'B', 'C', 'B', 'D', 'C', 'A', 'B', 'A', 'C']); + }); +}); diff --git a/src/algorithms/sets/shortest-common-supersequence/shortestCommonSupersequence.js b/src/algorithms/sets/shortest-common-supersequence/shortestCommonSupersequence.js new file mode 100644 index 00000000..6998e0d1 --- /dev/null +++ b/src/algorithms/sets/shortest-common-supersequence/shortestCommonSupersequence.js @@ -0,0 +1,71 @@ +import longestCommonSubsequnce from '../longest-common-subsequnce/longestCommonSubsequnce'; + +/** + * @param {string[]} set1 + * @param {string[]} set2 + * @return {string[]} + */ + +export default function shortestCommonSupersequence(set1, set2) { + // Let's first find the longest common subsequence of two sets. + const lcs = longestCommonSubsequnce(set1, set2); + + // If LCS is empty then the shortest common supersequnce would be just + // concatenation of two sequences. + if (lcs.length === 1 && lcs[0] === '') { + return set1.concat(set2); + } + + // Now let's add elements of set1 and set2 in order before/inside/after the LCS. + let supersequence = []; + + let setIndex1 = 0; + let setIndex2 = 0; + let lcsIndex = 0; + let setOnHold1 = false; + let setOnHold2 = false; + + while (lcsIndex < lcs.length) { + // Add elements of the first set to supersequence in correct order. + if (setIndex1 < set1.length) { + if (!setOnHold1 && set1[setIndex1] !== lcs[lcsIndex]) { + supersequence.push(set1[setIndex1]); + setIndex1 += 1; + } else { + setOnHold1 = true; + } + } + + // Add elements of the second set to supersequence in correct order. + if (setIndex2 < set2.length) { + if (!setOnHold2 && set2[setIndex2] !== lcs[lcsIndex]) { + supersequence.push(set2[setIndex2]); + setIndex2 += 1; + } else { + setOnHold2 = true; + } + } + + // Add LCS element to the supersequence in correct order. + if (setOnHold1 && setOnHold2) { + supersequence.push(lcs[lcsIndex]); + lcsIndex += 1; + setIndex1 += 1; + setIndex2 += 1; + setOnHold1 = false; + setOnHold2 = false; + } + } + + // Attach set1 leftovers. + if (setIndex1 < set1.length) { + supersequence = supersequence.concat(set1.slice(setIndex1)); + } + + // Attach set2 leftovers. + if (setIndex2 < set2.length) { + supersequence = supersequence.concat(set2.slice(setIndex2)); + } + + return supersequence; +}