300. Longest Increasing Subsequence
At a Glance
- Topic: dp-1d
- Pattern: Dynamic Programming (
O(n^2)); patience sorting + binary search (O(n log n)) - Difficulty: Medium
- Companies: Google, Amazon, Microsoft, Bloomberg, Facebook
- Frequency: Very High
- LeetCode: 300
Problem (one-liner)
Given integer array nums, return the length of the longest strictly increasing subsequence (not necessarily contiguous). Input: nums. Output: length (non-negative).
Recognition Cues
- “Longest increasing subsequence” / LIS
- Subsequence (can skip elements), not substring
- Patience piles → binary search for length in
O(n log n)
Diagram
At-a-glance flow (replace with problem-specific Mermaid as you refine this note). camelCase node IDs; no spaces in IDs.
Loading diagram…
Approaches
- Brute force — all
2^nbitmasks — exponential. - Better —
dp[index] = 1 + max(dp[j])forj < indexandnums[j] < nums[index]—O(n^2)time /O(n)space. - Optimal — maintain sorted “tails” array, binary search position —
O(n log n)time /O(n)space. <- pick this in interview.
Optimal Solution
Go
package main
func lengthOfLISTable(nums []int) int {
length := len(nums)
if length == 0 {
return 0
}
dp := make([]int, length)
best := 1
for right := 0; right < length; right++ {
dp[right] = 1
for left := 0; left < right; left++ {
if nums[left] < nums[right] && dp[left]+1 > dp[right] {
dp[right] = dp[left] + 1
}
}
if dp[right] > best {
best = dp[right]
}
}
return best
}
func lengthOfLIS(nums []int) int {
tails := make([]int, 0, len(nums))
for _, value := range nums {
left, right := 0, len(tails)
for left < right {
mid := left + (right-left)/2
if tails[mid] < value {
left = mid + 1
} else {
right = mid
}
}
if left == len(tails) {
tails = append(tails, value)
} else {
tails[left] = value
}
}
return len(tails)
}JavaScript
function lengthOfLISTable(nums) {
const length = nums.length;
if (length === 0) return 0;
const dp = new Array(length).fill(1);
let best = 1;
for (let right = 0; right < length; right++) {
for (let left = 0; left < right; left++) {
if (nums[left] < nums[right]) {
dp[right] = Math.max(dp[right], dp[left] + 1);
}
}
best = Math.max(best, dp[right]);
}
return best;
}
function lengthOfLIS(nums) {
const tails = [];
for (const value of nums) {
let left = 0;
let right = tails.length;
while (left < right) {
const mid = Math.floor((left + right) / 2);
if (tails[mid] < value) {
left = mid + 1;
} else {
right = mid;
}
}
if (left === tails.length) {
tails.push(value);
} else {
tails[left] = value;
}
}
return tails.length;
}Walkthrough
Input: nums = [10, 9, 2, 5, 3, 7, 101, 18]
Patience tails after each value:
10→[10]9→[9]2→[2]5→[2,5]3→[2,3]7→[2,3,7]101→[2,3,7,101]18→[2,3,7,18]
Length 4 (e.g. 2,3,7,101 before last replace). Invariant: tails is smallest possible tail values for increasing sequences of each length.
Edge Cases
- Empty array →
0 - Strict decrease → length
1 - Duplicates do not extend (
nums[left]must be strictly less)
Pitfalls
- Confusing LIS with longest contiguous increasing run
- Using
<=instead of<breaks strict increase - Binary search uses lower-bound on
tailsfor replacement site
Similar Problems
- 322. Coin Change — different DP, same “build table from prefix” habit.
- 416. Partition Equal Subset Sum — boolean subset feasibility.
- 1143. Longest Common Subsequence — classic order-preserving subsequence DP.
Variants
- Longest non-decreasing → adjust comparisons.
- Reconstruct one actual LIS → parent pointers on DP table.
Mind-Map Tags
#lis #patience-sorting #binary-search-on-answer #dp-1d #medium
Last updated on
Spotted something unclear or wrong on this page?