diff --git a/src/algorithms/sets/longest-increasing-subsequence/dpLongestIncreasingSubsequence.js b/src/algorithms/sets/longest-increasing-subsequence/dpLongestIncreasingSubsequence.js index e5f2ec6d..55fbd4dc 100644 --- a/src/algorithms/sets/longest-increasing-subsequence/dpLongestIncreasingSubsequence.js +++ b/src/algorithms/sets/longest-increasing-subsequence/dpLongestIncreasingSubsequence.js @@ -1,53 +1,41 @@ /** - * Dynamic programming approach to find longest increasing subsequence. - * Complexity: O(n * n) + * Optimized approach to find longest increasing subsequence. + * Complexity: O(n * log 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); + if (sequence.length === 0) { + return 0; + } - let previousElementIndex = 0; - let currentElementIndex = 1; + // This will store the smallest tail value for all increasing subsequences + // with length i+1 in tail[i]. + const tails = []; - 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; + sequence.forEach((num) => { + let left = 0; + let right = tails.length; + + // Binary search for the insertion point of the current element. + while (left < right) { + const mid = Math.floor((left + right) / 2); + if (tails[mid] < num) { + left = mid + 1; + } else { + right = mid; } } - // 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; + // If left is equal to the length of tails, it means we are adding a new subsequence. + if (left === tails.length) { + tails.push(num); + } else { + // Otherwise, we are updating an existing subsequence with a new tail value. + tails[left] = num; } - } + }); - // 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; + return tails.length; }