Facebook Pixel

300. Longest Increasing Subsequence

Problem Description

Given an integer array nums, you need to find the length of the longest strictly increasing subsequence.

A subsequence is a sequence that can be derived from the array by deleting some or no elements without changing the order of the remaining elements. For example, [3,6,2,7] is the array, then [3,6,7] is a subsequence (we deleted the element 2), but [6,3,7] is not a valid subsequence (the order was changed).

A strictly increasing subsequence means each element must be strictly greater than the previous one (not equal to).

For example:

  • If nums = [10,9,2,5,3,7,101,18], the longest strictly increasing subsequence is [2,3,7,18] or [2,3,7,101], both with length 4.
  • If nums = [0,1,0,3,2,3], the longest strictly increasing subsequence could be [0,1,2,3] with length 4.

The solution uses dynamic programming where f[i] represents the length of the longest increasing subsequence ending at index i. For each position i, it checks all previous positions j where nums[j] < nums[i], and updates f[i] to be the maximum of its current value or f[j] + 1. The final answer is the maximum value in the f array.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

Think about building the longest increasing subsequence step by step. At each position in the array, we need to make a decision: what's the longest increasing subsequence that can end at this position?

The key insight is that for any element at position i, we can extend any increasing subsequence that ends at a previous position j (where j < i) as long as nums[j] < nums[i]. This is because if we have an increasing subsequence ending at j, and nums[i] is greater than nums[j], we can simply append nums[i] to that subsequence to create a longer one.

For example, if we have nums = [2, 5, 3, 7]:

  • At position 0 (value 2): The longest subsequence ending here has length 1 (just [2])
  • At position 1 (value 5): We can extend the subsequence ending at position 0 since 2 < 5, giving us length 2 ([2, 5])
  • At position 2 (value 3): We can extend the subsequence from position 0 since 2 < 3, giving us length 2 ([2, 3])
  • At position 3 (value 7): We can extend subsequences from positions 0, 1, or 2. The best choice is extending from position 1 or 2, both giving length 3

This naturally leads to a dynamic programming approach where we track f[i] = the length of the longest increasing subsequence ending at position i. For each position, we look back at all previous positions, check if we can extend their subsequences (by comparing values), and take the best option. The overall answer is the maximum length found across all positions.

Solution Approach

The solution uses dynamic programming with a 1D array to track the longest increasing subsequence ending at each position.

Data Structure:

  • Create an array f of size n where f[i] represents the length of the longest increasing subsequence ending at index i
  • Initialize all values in f to 1, since each element by itself forms a subsequence of length 1

Algorithm Steps:

  1. Initialization: Set f = [1] * n because the minimum length of any subsequence ending at any position is 1 (the element itself).

  2. Fill the DP array: For each position i from 1 to n-1:

    • Check all previous positions j from 0 to i-1
    • If nums[j] < nums[i], it means we can extend the subsequence ending at j by adding nums[i]
    • Update f[i] = max(f[i], f[j] + 1) to keep the maximum length possible
  3. Return the result: The answer is max(f), which gives us the maximum length among all possible ending positions.

Time Complexity: O(n²) where n is the length of the array, as we have two nested loops.

Space Complexity: O(n) for the DP array f.

Example walkthrough with nums = [10, 9, 2, 5, 3, 7]:

  • Initial: f = [1, 1, 1, 1, 1, 1]
  • At i=1: 9 is not greater than 10, so f[1] stays 1
  • At i=2: 2 is not greater than 10 or 9, so f[2] stays 1
  • At i=3: 5 > 2, so f[3] = f[2] + 1 = 2
  • At i=4: 3 > 2, so f[4] = f[2] + 1 = 2
  • At i=5: 7 > 2, 5, 3, best is from position 3 or 4, so f[5] = 3
  • Final: f = [1, 1, 1, 2, 2, 3], return 3

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example with nums = [4, 2, 5, 3, 7] to illustrate the solution approach.

Step 1: Initialize DP array

  • f = [1, 1, 1, 1, 1] (each element forms a subsequence of length 1)

Step 2: Process each position

Position i=1 (value=2):

  • Check j=0: nums[0]=4 > nums[1]=2, cannot extend
  • f[1] remains 1

Position i=2 (value=5):

  • Check j=0: nums[0]=4 < nums[2]=5, can extend! f[2] = max(1, f[0]+1) = 2
  • Check j=1: nums[1]=2 < nums[2]=5, can extend! f[2] = max(2, f[1]+1) = 2
  • f[2] = 2 (subsequence could be [4,5] or [2,5])

Position i=3 (value=3):

  • Check j=0: nums[0]=4 > nums[3]=3, cannot extend
  • Check j=1: nums[1]=2 < nums[3]=3, can extend! f[3] = max(1, f[1]+1) = 2
  • Check j=2: nums[2]=5 > nums[3]=3, cannot extend
  • f[3] = 2 (subsequence is [2,3])

Position i=4 (value=7):

  • Check j=0: nums[0]=4 < nums[4]=7, can extend! f[4] = max(1, f[0]+1) = 2
  • Check j=1: nums[1]=2 < nums[4]=7, can extend! f[4] = max(2, f[1]+1) = 2
  • Check j=2: nums[2]=5 < nums[4]=7, can extend! f[4] = max(2, f[2]+1) = 3
  • Check j=3: nums[3]=3 < nums[4]=7, can extend! f[4] = max(3, f[3]+1) = 3
  • f[4] = 3 (best subsequence is [2,5,7] or [2,3,7])

Step 3: Find maximum

  • Final DP array: f = [1, 1, 2, 2, 3]
  • Return max(f) = 3

The longest increasing subsequence has length 3, such as [2, 5, 7] or [2, 3, 7].

Solution Implementation

1class Solution:
2    def lengthOfLIS(self, nums: List[int]) -> int:
3        """
4        Find the length of the longest increasing subsequence.
5      
6        Args:
7            nums: List of integers
8          
9        Returns:
10            Length of the longest increasing subsequence
11        """
12        n = len(nums)
13      
14        # dp[i] represents the length of the longest increasing subsequence 
15        # ending at index i
16        dp = [1] * n
17      
18        # For each position i, check all previous positions j
19        for i in range(1, n):
20            for j in range(i):
21                # If nums[j] < nums[i], we can extend the subsequence ending at j
22                if nums[j] < nums[i]:
23                    # Update dp[i] to be the maximum of current value or 
24                    # the length of subsequence ending at j plus 1
25                    dp[i] = max(dp[i], dp[j] + 1)
26      
27        # Return the maximum length among all subsequences
28        return max(dp)
29
1class Solution {
2    /**
3     * Finds the length of the longest increasing subsequence in the given array.
4     * Uses dynamic programming approach where dp[i] represents the length of 
5     * the longest increasing subsequence ending at index i.
6     * 
7     * @param nums the input array of integers
8     * @return the length of the longest increasing subsequence
9     */
10    public int lengthOfLIS(int[] nums) {
11        int n = nums.length;
12      
13        // dp[i] stores the length of the longest increasing subsequence ending at index i
14        int[] dp = new int[n];
15      
16        // Initialize all positions with 1 (each element is a subsequence of length 1)
17        Arrays.fill(dp, 1);
18      
19        // Track the maximum length found so far
20        int maxLength = 1;
21      
22        // For each position i, check all previous positions j
23        for (int i = 1; i < n; i++) {
24            // Check all elements before current position
25            for (int j = 0; j < i; j++) {
26                // If nums[j] < nums[i], we can extend the subsequence ending at j
27                if (nums[j] < nums[i]) {
28                    // Update dp[i] to be the maximum of current value or extending from j
29                    dp[i] = Math.max(dp[i], dp[j] + 1);
30                }
31            }
32            // Update the global maximum length
33            maxLength = Math.max(maxLength, dp[i]);
34        }
35      
36        return maxLength;
37    }
38}
39
1class Solution {
2public:
3    int lengthOfLIS(vector<int>& nums) {
4        int n = nums.size();
5      
6        // dp[i] represents the length of the longest increasing subsequence 
7        // ending at index i
8        vector<int> dp(n, 1);
9      
10        // Iterate through each position as potential end of subsequence
11        for (int i = 1; i < n; ++i) {
12            // Check all previous elements that could form an increasing subsequence
13            for (int j = 0; j < i; ++j) {
14                // If current element is greater than previous element,
15                // we can extend the subsequence ending at j
16                if (nums[j] < nums[i]) {
17                    dp[i] = max(dp[i], dp[j] + 1);
18                }
19            }
20        }
21      
22        // Return the maximum length among all possible ending positions
23        return *max_element(dp.begin(), dp.end());
24    }
25};
26
1/**
2 * Finds the length of the longest increasing subsequence in an array
3 * @param nums - The input array of numbers
4 * @returns The length of the longest increasing subsequence
5 */
6function lengthOfLIS(nums: number[]): number {
7    // Get the length of the input array
8    const arrayLength: number = nums.length;
9  
10    // Initialize DP array where dp[i] represents the length of 
11    // the longest increasing subsequence ending at index i
12    const dp: number[] = new Array(arrayLength).fill(1);
13  
14    // Iterate through each position in the array starting from index 1
15    for (let currentIndex = 1; currentIndex < arrayLength; ++currentIndex) {
16        // Check all previous elements before the current index
17        for (let previousIndex = 0; previousIndex < currentIndex; ++previousIndex) {
18            // If we find a smaller element before current position,
19            // we can potentially extend that subsequence
20            if (nums[previousIndex] < nums[currentIndex]) {
21                // Update dp[currentIndex] to be the maximum of:
22                // - its current value
23                // - the length of subsequence ending at previousIndex plus 1
24                dp[currentIndex] = Math.max(dp[currentIndex], dp[previousIndex] + 1);
25            }
26        }
27    }
28  
29    // Return the maximum value in the dp array,
30    // which represents the longest increasing subsequence in the entire array
31    return Math.max(...dp);
32}
33

Time and Space Complexity

Time Complexity: O(n²)

The algorithm uses two nested loops:

  • The outer loop iterates from index 1 to n-1, executing n-1 iterations
  • For each iteration i of the outer loop, the inner loop iterates from index 0 to i-1, executing i iterations
  • The total number of operations is: 1 + 2 + 3 + ... + (n-1) = n(n-1)/2
  • Therefore, the time complexity is O(n²)

Space Complexity: O(n)

The algorithm uses:

  • An array f of size n to store the length of the longest increasing subsequence ending at each index, requiring O(n) space
  • A few constant variables (n, i, j) which require O(1) space
  • Therefore, the overall space complexity is O(n)

Common Pitfalls

1. Empty Array Handling

The current implementation fails when the input array is empty. Calling max(dp) on an empty list will raise a ValueError.

Problem Example:

nums = []
# This will crash with: ValueError: max() arg is an empty sequence

Solution: Add an edge case check at the beginning:

def lengthOfLIS(self, nums: List[int]) -> int:
    if not nums:
        return 0
  
    n = len(nums)
    dp = [1] * n
    # ... rest of the code

2. Confusion Between "Strictly Increasing" vs "Non-Decreasing"

A common mistake is using nums[j] <= nums[i] instead of nums[j] < nums[i], which would allow equal elements in the subsequence.

Problem Example:

nums = [1, 3, 3, 4]
# Wrong: Using <= would give length 4, treating [1,3,3,4] as valid
# Correct: Using < gives length 3, with subsequence [1,3,4]

Solution: Always use strict inequality (<) for strictly increasing subsequences:

if nums[j] < nums[i]:  # NOT nums[j] <= nums[i]
    dp[i] = max(dp[i], dp[j] + 1)

3. Performance Issue with Large Arrays

The O(n²) solution becomes inefficient for large arrays (n > 10⁴). This can cause time limit exceeded errors.

Problem Example:

nums = list(range(10000))  # Already sorted array with 10,000 elements
# This will perform 50 million comparisons

Solution: Use binary search optimization with an auxiliary array for O(n log n) complexity:

def lengthOfLIS(self, nums: List[int]) -> int:
    from bisect import bisect_left
  
    if not nums:
        return 0
  
    # tails[i] stores the smallest tail element for LIS of length i+1
    tails = []
  
    for num in nums:
        pos = bisect_left(tails, num)
        if pos == len(tails):
            tails.append(num)
        else:
            tails[pos] = num
  
    return len(tails)

4. Incorrect Initialization Value

Some might initialize dp with 0 instead of 1, forgetting that each element forms a subsequence of length 1 by itself.

Problem Example:

dp = [0] * n  # Wrong initialization
# This would give incorrect results as it assumes elements have length 0

Solution: Always initialize with 1:

dp = [1] * n  # Correct: each element is a subsequence of length 1
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What is the best way of checking if an element exists in a sorted array once in terms of time complexity? Select the best that applies.


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More