diff --git a/README.md b/README.md index cb95ca35..72884a98 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ * [Knuth–Morris–Pratt Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/string/knuth-morris-pratt) - substring search * [Rabin Karp Algorithm](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/string/rabin-karp) - substring search * [Longest Common Subsequence](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/string/longest-common-subsequnce) (LCS) - * longest common substring + * [Longest Common Substring](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/string/longest-common-substring) * **Search** * [Binary Search](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/search/binary-search) * **Sorting** @@ -74,6 +74,7 @@ * **Dynamic Programming** * [Levenshtein Distance](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/string/levenshtein-distance) - minimum edit distance between two sequences * [Longest Common Subsequence](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/string/longest-common-subsequnce) (LCS) + * [Longest Common Substring](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/string/longest-common-substring) * Increasing subsequence * Knapsack problem * Maximum subarray diff --git a/src/algorithms/string/longest-common-substring/README.md b/src/algorithms/string/longest-common-substring/README.md new file mode 100644 index 00000000..3b4dded3 --- /dev/null +++ b/src/algorithms/string/longest-common-substring/README.md @@ -0,0 +1,24 @@ +# Longest Common Substring Problem + +The longest common substring problem is to find the longest string +(or strings) that is a substring (or are substrings) of two or more +strings. + +## Example + +The longest common substring of the strings `ABABC`, `BABCA` and +`ABCBA` is string `ABC` of length 3. Other common substrings are +`A`, `AB`, `B`, `BA`, `BC` and `C`. + +``` +ABABC + ||| + BABCA + ||| + ABCBA +``` + +## References + +- [Wikipedia](https://en.wikipedia.org/wiki/Longest_common_substring_problem) +- [YouTube](https://www.youtube.com/watch?v=BysNXJHzCEs) diff --git a/src/algorithms/string/longest-common-substring/__test__/longestCommonSubstring.test.js b/src/algorithms/string/longest-common-substring/__test__/longestCommonSubstring.test.js new file mode 100644 index 00000000..55c7d4da --- /dev/null +++ b/src/algorithms/string/longest-common-substring/__test__/longestCommonSubstring.test.js @@ -0,0 +1,11 @@ +import longestCommonSubstring from '../longestCommonSubstring'; + +describe('longestCommonSubstring', () => { + it('should find longest common substring between two strings', () => { + expect(longestCommonSubstring('', '')).toBe(''); + expect(longestCommonSubstring('ABC', '')).toBe(''); + expect(longestCommonSubstring('', 'ABC')).toBe(''); + expect(longestCommonSubstring('ABABC', 'BABCA')).toBe('BABC'); + expect(longestCommonSubstring('BABCA', 'ABCBA')).toBe('ABC'); + }); +}); diff --git a/src/algorithms/string/longest-common-substring/longestCommonSubstring.js b/src/algorithms/string/longest-common-substring/longestCommonSubstring.js new file mode 100644 index 00000000..10919bf6 --- /dev/null +++ b/src/algorithms/string/longest-common-substring/longestCommonSubstring.js @@ -0,0 +1,59 @@ +/** + * @param {string} s1 + * @param {string} s2 + * @return {string} + */ +export default function longestCommonSubstring(s1, s2) { + // Init the matrix of all substring lengths to use Dynamic Programming approach. + const substringMatrix = Array(s2.length + 1).fill(null).map(() => { + return Array(s1.length + 1).fill(null); + }); + + // Fill the first row and first column with zeros to provide initial values. + for (let columnIndex = 0; columnIndex <= s1.length; columnIndex += 1) { + substringMatrix[0][columnIndex] = 0; + } + + for (let rowIndex = 0; rowIndex <= s2.length; rowIndex += 1) { + substringMatrix[rowIndex][0] = 0; + } + + // Build the matrix of all substring lengths to use Dynamic Programming approach. + let longestSubstringLength = 0; + let longestSubstringColumn = 0; + let longestSubstringRow = 0; + + for (let rowIndex = 1; rowIndex <= s2.length; rowIndex += 1) { + for (let columnIndex = 1; columnIndex <= s1.length; columnIndex += 1) { + if (s1[columnIndex - 1] === s2[rowIndex - 1]) { + substringMatrix[rowIndex][columnIndex] = substringMatrix[rowIndex - 1][columnIndex - 1] + 1; + } else { + substringMatrix[rowIndex][columnIndex] = 0; + } + + // Try to find the biggest length of all common substring lengths + // and to memorize its last character position (indices) + if (substringMatrix[rowIndex][columnIndex] > longestSubstringLength) { + longestSubstringLength = substringMatrix[rowIndex][columnIndex]; + longestSubstringColumn = columnIndex; + longestSubstringRow = rowIndex; + } + } + } + + if (longestSubstringLength === 0) { + // Longest common substring has not been found. + return ''; + } + + // Detect the longest substring from the matrix. + let longestSubstring = ''; + + while (substringMatrix[longestSubstringRow][longestSubstringColumn] > 0) { + longestSubstring = s1[longestSubstringColumn - 1] + longestSubstring; + longestSubstringRow -= 1; + longestSubstringColumn -= 1; + } + + return longestSubstring; +}