diff --git a/README.md b/README.md index 3cbfe6cd..6cf67b47 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ * [Combinations](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sets/combinations) (with and without repetitions) * [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) * **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,8 +93,7 @@ * [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/sets/longest-common-subsequnce) (LCS) * [Longest Common Substring](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/string/longest-common-substring) - * Increasing subsequence - * Longest Increasing subsequence + * [Longest Increasing subsequence](https://github.com/trekhleb/javascript-algorithms/tree/master/src/algorithms/sets/longest-increasing-subsequence) * Shortest common supersequence * Knapsack problem * Maximum subarray diff --git a/src/algorithms/sets/longest-increasing-subsequence/README.md b/src/algorithms/sets/longest-increasing-subsequence/README.md new file mode 100644 index 00000000..1011a1b8 --- /dev/null +++ b/src/algorithms/sets/longest-increasing-subsequence/README.md @@ -0,0 +1,46 @@ +# Longest Increasing Subsequence + +The longest increasing subsequence problem is to find a subsequence of a +given sequence in which the subsequence's elements are in sorted order, +lowest to highest, and in which the subsequence is as long as possible. +This subsequence is not necessarily contiguous, or unique. + +## Complexity + +The longest increasing subsequence problem is solvable in +time `O(n log n)`, where `n` denotes the length of the input sequence. + +Dynamic programming approach has complexity `O(n * n)`. + +## Example + +In the first 16 terms of the binary Van der Corput sequence + +``` +0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15 +``` + +a longest increasing subsequence is + +``` +0, 2, 6, 9, 11, 15. +``` + +This subsequence has length six; +the input sequence has no seven-member increasing subsequences. +The longest increasing subsequence in this example is not unique: for +instance, + +``` +0, 4, 6, 9, 11, 15 or +0, 2, 6, 9, 13, 15 or +0, 4, 6, 9, 13, 15 +``` + +are other increasing subsequences of equal length in the same +input sequence. + +## References + +- [Wikipedia](https://en.wikipedia.org/wiki/Longest_increasing_subsequence) +- [Dynamic Programming Approach on YouTube](https://www.youtube.com/watch?v=CE2b_-XfVDk) diff --git a/src/algorithms/sets/longest-increasing-subsequence/__test__/dpLongestIncreasingSubsequence.test.js b/src/algorithms/sets/longest-increasing-subsequence/__test__/dpLongestIncreasingSubsequence.test.js new file mode 100644 index 00000000..13160b61 --- /dev/null +++ b/src/algorithms/sets/longest-increasing-subsequence/__test__/dpLongestIncreasingSubsequence.test.js @@ -0,0 +1,36 @@ +import dpLongestIncreasingSubsequence from '../dpLongestIncreasingSubsequence'; + +describe('dpLongestIncreasingSubsequence', () => { + it('should find longest increasing subsequence length', () => { + // Should be: + // 9 or + // 8 or + // 7 or + // 6 or + // ... + expect(dpLongestIncreasingSubsequence([ + 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, + ])).toBe(1); + + // Should be: + // 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 + expect(dpLongestIncreasingSubsequence([ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + ])).toBe(10); + + // Should be: + // -1, 0, 2, 3 + expect(dpLongestIncreasingSubsequence([ + 3, 4, -1, 0, 6, 2, 3, + ])).toBe(4); + + // Should be: + // 0, 2, 6, 9, 11, 15 or + // 0, 4, 6, 9, 11, 15 or + // 0, 2, 6, 9, 13, 15 or + // 0, 4, 6, 9, 13, 15 + expect(dpLongestIncreasingSubsequence([ + 0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15, + ])).toBe(6); + }); +}); diff --git a/src/algorithms/sets/longest-increasing-subsequence/dpLongestIncreasingSubsequence.js b/src/algorithms/sets/longest-increasing-subsequence/dpLongestIncreasingSubsequence.js new file mode 100644 index 00000000..e5f2ec6d --- /dev/null +++ b/src/algorithms/sets/longest-increasing-subsequence/dpLongestIncreasingSubsequence.js @@ -0,0 +1,53 @@ +/** + * Dynamic programming approach to find longest increasing subsequence. + * Complexity: O(n * n) + * + * @param {number[]} sequence + * @return {number} + */ +export default function dpLongestIncreasingSubsequence(sequence) { + // Create array with longest increasing substrings length and + // fill it with 1-s that would mean that each element of the sequence + // is itself a minimum increasing subsequence. + const lengthsArray = Array(sequence.length).fill(1); + + let previousElementIndex = 0; + let currentElementIndex = 1; + + while (currentElementIndex < sequence.length) { + if (sequence[previousElementIndex] < sequence[currentElementIndex]) { + // If current element is bigger then the previous one then + // current element is a part of increasing subsequence which + // length is by one bigger then the length of increasing subsequence + // for previous element. + const newLength = lengthsArray[previousElementIndex] + 1; + if (newLength > lengthsArray[currentElementIndex]) { + // Increase only if previous element would give us bigger subsequence length + // then we already have for current element. + lengthsArray[currentElementIndex] = newLength; + } + } + + // Move previous element index right. + previousElementIndex += 1; + + // If previous element index equals to current element index then + // shift current element right and reset previous element index to zero. + if (previousElementIndex === currentElementIndex) { + currentElementIndex += 1; + previousElementIndex = 0; + } + } + + // Find the biggest element in lengthsArray. + // This number is the biggest length of increasing subsequence. + let longestIncreasingLength = 0; + + for (let i = 0; i < lengthsArray.length; i += 1) { + if (lengthsArray[i] > longestIncreasingLength) { + longestIncreasingLength = lengthsArray[i]; + } + } + + return longestIncreasingLength; +}