Question

Given an unsorted array of integers, find the length of longest increasing subsequence.

Example:

Input: [10,9,2,5,3,7,101,18]
Output: 4
Explanation: The longest increasing subsequence is [2,3,7,101], therefore the length is 4.

Note:

  • There may be more than one LIS combination, it is only necessary for you to return the length.
  • Your algorithm should run in \(O(n^2)\) complexity.

Follow up: Could you improve it to \(O(n log n)\) time complexity?

\(O(n^2)\) DP Solution

class Solution(object):
    def lengthOfLIS(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        if len(nums) == 0:
            return 0

        dp = [1] * len(nums)

        for i in range(1, len(nums)):
            for j in range(i):
                if nums[i] > nums[j]:
                    dp[i] = max(dp[i], dp[j] + 1)

        return max(dp)

\(O(n log n)\) Solution

In this approach, we scan the array from left to right. We also make use of a dp array initialized with all 0’s. This dp array is meant to store the increasing subsequence up to and including the currently encountered element. For the element corresponding to the \(j^{th}\) index nums[j], we determine the correct position in the dp array, say \(i^{th}\) index, such that dp[:i+1] forms a increasing subsequence including nums[j]. This can be achieved by binary search. Instead of inserting nums[j] into \(i^{th}\) index of dp array, we override the existing value of dp[i]. We also keep track of the length of LIS so far, which would be the largest i plus 1. Therefore, after traversing through nums, we know the length of LIS, though the dp does not store the content of it.

The time complexity is \(O(nlogn)\) because we perform \(n\) binary searches. The space complexity is \(O(n)\).

In Python, we can use bisect module to perform binary search. Note that it will return the index left to the same value if the value exists in the array, so we need to handle that.

from bisect import bisect_left
class Solution(object):
    def lengthOfLIS(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        if len(nums) <= 1:
            return len(nums)

        dp = [0] * len(nums)
        length = 0
        for n in nums:
            idx = bisect_left(dp, n, 0, length)
            if idx + 1 >= len(dp) or dp[idx + 1] != n:
                dp[idx] = n
            if idx == length:
                length += 1

        return length