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.
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 sizen
wheref[i]
represents the length of the longest increasing subsequence ending at indexi
- Initialize all values in
f
to 1, since each element by itself forms a subsequence of length 1
Algorithm Steps:
-
Initialization: Set
f = [1] * n
because the minimum length of any subsequence ending at any position is 1 (the element itself). -
Fill the DP array: For each position
i
from 1 ton-1
:- Check all previous positions
j
from 0 toi-1
- If
nums[j] < nums[i]
, it means we can extend the subsequence ending atj
by addingnums[i]
- Update
f[i] = max(f[i], f[j] + 1)
to keep the maximum length possible
- Check all previous positions
-
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 EvaluatorExample 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
ton-1
, executingn-1
iterations - For each iteration
i
of the outer loop, the inner loop iterates from index0
toi-1
, executingi
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 sizen
to store the length of the longest increasing subsequence ending at each index, requiringO(n)
space - A few constant variables (
n
,i
,j
) which requireO(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
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
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!