Skip to content
DSAPrep

    ↑↓ to move · Enter to open · Esc to close

    Medium1-D DP

    Longest Increasing Subsequence

    Given an integer array nums, return the length of the longest strictly increasing subsequence.

    Example 1

    Input: nums = [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.

    Example 2

    Input: nums = [0,1,0,3,2,3]

    Output: 4

    Example 3

    Input: nums = [7,7,7,7,7,7,7]

    Output: 1

    Constraints

    • 1 <= nums.length <= 2500
    • -10^4 <= nums[i] <= 10^4
    Follow-up: Can you come up with an algorithm that runs in O(n log(n)) time complexity?
    View original on LeetCode ↗

    Define dp[i] as the length of the longest increasing subsequence that ends exactly at index i. Any such subsequence either is just nums[i] alone (length 1), or extends some earlier increasing subsequence ending at a smaller value: for every j < i with nums[j] < nums[i], dp[i] could be dp[j] + 1. Taking the best such extension gives the recurrence, and the overall answer is the max over all dp[i].

    Brute Force Recursion

    Time O(2ⁿ)Space O(n)

    Define lengthEndingAt(i) recursively: it is 1 plus the best lengthEndingAt(j) over all valid earlier indices j < i with nums[j] < nums[i], or just 1 if there is no such j.

    class Solution:
    def lengthOfLIS(self, nums: list[int]) -> int:
    n = len(nums)
    def lengthEndingAt(i: int) -> int:
    best = 1
    for j in range(i):
    if nums[j] < nums[i]:
    best = max(best, 1 + lengthEndingAt(j))
    return best
    return max(lengthEndingAt(i) for i in range(n))

    Each call can recurse into up to i earlier calls, and those overlap heavily across different starting indices, giving exponential O(2ⁿ) time in the worst case, O(n) recursion depth.

    Bottom-Up DP

    Time O(n²)Space O(n)

    Fill dp[0..n-1] left to right, each starting at 1 (the subsequence containing just nums[i]), then scanning every earlier index j for a smaller value to extend.

    class Solution:
    def lengthOfLIS(self, nums: list[int]) -> int:
    n = len(nums)
    dp = [1] * n
    for i in range(n):
    for j in range(i):
    if nums[j] < nums[i]:
    dp[i] = max(dp[i], dp[j] + 1)
    return max(dp)

    Trace for nums = [10, 9, 2, 5, 3, 7, 101, 18] (only the winning predecessor is shown as a pointer where one exists):

    1
    0
    ·
    1
    ·
    2
    ·
    3
    ·
    4
    ·
    5
    ·
    6
    ·
    7
    1 / 8
    comparingresult

    dp[0] = 1: "10" alone. No earlier elements to extend from.

    For each of the n indices, up to n earlier indices are checked → O(n²) time, O(n) space for the dp array.

    Patience Sorting (Binary Search)

    OptimalTime O(n log n)Space O(n)

    Maintain an auxiliary array tails, where tails[k] is the smallest possible tail value of any increasing subsequence of length k + 1 seen so far. For each new number, binary search tails for the first entry >= num: if found, replace it (a smaller tail keeps future extensions easier); if not found, num extends the longest subsequence so far, so append it. The final length of tails is the answer – note tails itself does not necessarily hold an actual valid subsequence, only correct lengths.

    from bisect import bisect_left
    class Solution:
    def lengthOfLIS(self, nums: list[int]) -> int:
    tails = []
    for num in nums:
    idx = bisect_left(tails, num)
    if idx == len(tails):
    tails.append(num)
    else:
    tails[idx] = num
    return len(tails)

    Watch each number binary-search tails: a smaller tail overwrites, and only a number bigger than every tail grows the answer.

    Input nums

    109253710118

    tails[k] = smallest tail of length k + 1

    longest so far: 0
    L1
    L2
    L3
    L4
    L5
    L6
    L7
    L8

    binary search: no tails yet

    1 / 9
    currentcomparingresult

    Start empty. tails[k] will hold the smallest tail of any increasing subsequence of length k + 1.

    Walking through nums = [10, 9, 2, 5, 3, 7, 101, 18]: tails evolves as [10] → [9] (10 replaced, since 9 is a smaller tail for length-1) → [2] → [2,5] → [2,3] (5 replaced by 3, a smaller tail for length-2) → [2,3,7] → [2,3,7,101] → [2,3,7,18] (101 replaced by 18). Final length is 4, matching the expected answer, even though [2,3,7,18] itself is not the actual LIS found earlier.

    Each of the n numbers does one binary search (and possibly one write) over an array of size at most n → O(n log n) time, O(n) space for tails. This meets the problem’s follow-up requirement and is the best known complexity for this problem.

    More 1-D DP practice