Longest Increasing Subsequence — Problem & Solution
Longest Increasing Subsequence solution explained: step-by-step approach, complexity and code in Python, Java, C++ and JavaScript. A medium problem.
Longest Increasing Subsequence is a medium coding problem on arrays, dynamic programming and binary search, asked in Intuit interviews.
- Difficulty: Medium
- Topics: Arrays, Dynamic Programming, Binary Search
- Asked at: Intuit
- Time limit: 2 s
- Memory limit: 256 MB
- Languages: JavaScript, TypeScript, Python, Java, C++, C, C#, Go, Kotlin, Swift, Rust, PHP and Ruby
Problem statement
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: [2,3,7,101] (or [2,5,7,101]).
Example 2
Input: nums = [7,7,7,7,7,7,7]
Output: 1
Constraints
1 <= nums.length <= 30-100 <= nums[i] <= 100
Follow-up: the O(n log n) 'patience sorting' solution keeps an array of smallest tail values.
Hints for Longest Increasing Subsequence
Hint 1
O(n²): dp[i] = 1 + max(dp[j]) over j < i with nums[j] < nums[i].
Hint 2
O(n log n): maintain tails[k] = smallest tail of an increasing subsequence of length k+1; binary-search each element in.
How to solve Longest Increasing Subsequence
Let best[i] be the length of the longest increasing subsequence ending exactly at index i. Any such subsequence's second-to-last element sits at some earlier index j with a smaller value, so best[i] = 1 + max(best[j]) over those j. The answer is the largest entry in the table.
Approach
- Set every
best[i]to1— each element alone is a subsequence. - For each
i, scan all earlierj. - If
nums[j] < nums[i], the subsequence ending atjcan be extended: takebest[j] + 1if it improvesbest[i]. - Return the maximum value in
best.
Why it works
Anchoring the state at the ending index is what removes the ambiguity — subsequences are not contiguous, so "the best up to i" is not enough information to extend correctly, but "the best ending at i" is. Every increasing subsequence has a unique last element, so scanning all endpoints covers each exactly once. An O(n log n) variant keeps a list of the smallest possible tail value for each length and binary-searches it, which matters at larger inputs.
Complexity
- Time —
O(n^2) - Space —
O(n)
Pitfalls
- The comparison is strict (
<); using<=counts equal values and turns this into non-decreasing subsequences. - The answer is the maximum over the whole table, not
best[n-1]— the longest run need not end at the last element. - Initialise to
1, not0; every element is a subsequence of length one. - An all-equal array answers
1, which is a good check of the strictness.
Reference solution
Python
from typing import List
import bisect
def lengthOfLIS(nums: List[int]) -> int:
tails = []
for x in nums:
i = bisect.bisect_left(tails, x)
if i == len(tails):
tails.append(x)
else:
tails[i] = x
return len(tails)Java
public static int lengthOfLIS(int[] nums) {
int n = nums.length;
int[] best = new int[n];
Arrays.fill(best, 1);
int answer = 1;
for (int i = 0; i < n; i++) {
for (int j = 0; j < i; j++) {
if (nums[j] < nums[i] && best[j] + 1 > best[i]) best[i] = best[j] + 1;
}
if (best[i] > answer) answer = best[i];
}
return answer;
}C++
int lengthOfLIS(vector<int>& nums) {
int n = (int) nums.size();
vector<int> best(n, 1);
int answer = 1;
for (int i = 0; i < n; i++) {
for (int j = 0; j < i; j++) {
if (nums[j] < nums[i] && best[j] + 1 > best[i]) best[i] = best[j] + 1;
}
if (best[i] > answer) answer = best[i];
}
return answer;
}JavaScript
var lengthOfLIS = function(nums) {
const tails = [];
for (const x of nums) {
let lo = 0, hi = tails.length;
while (lo < hi) {
const mid = (lo + hi) >> 1;
if (tails[mid] < x) lo = mid + 1;
else hi = mid;
}
tails[lo] = x;
}
return tails.length;
};Also on the editorial tab: C, C#, Go, Kotlin, PHP, Ruby, Rust, Swift, TypeScript.
All 1199 arrays problems · the whole catalogue
Learn the technique: Arrays · Dynamic Programming