Problem
LeetCode 300 — Longest Increasing Subsequence
Given an integer array nums, return the length of the longest strictly increasing subsequence.
Example
nums = [10, 9, 2, 5, 3, 7, 101, 18] → 4
One valid LIS: [2, 3, 7, 18]
Approach 1 — Steps to Reach Each Index: O(n²)
Keep a steps[] array of length n where steps[i] = how many elements (steps) are in the longest increasing subsequence ending at index i.
Initialize every index to 1 — each element is a subsequence of length 1 by itself.
Then for each i, try to extend forward to every j > i:
- If
nums[i] < nums[j], you can reachjin one more step fromi - Update
steps[j] = max(steps[j], steps[i] + 1)
Return max(steps).
Same O(n²) as the backward-looking DP — just flipped: instead of asking “who can extend to me?”, ask “who can I extend to?”.
Walkthrough
nums = [10, 9, 2, 5, 3, 7, 101, 18]
steps = [ 1, 1, 1, 1, 1, 1, 1, 1] // init
i=2 (2): extend to 5,3,7,101,18 → steps[3]=2, steps[4]=2, steps[5]=2, ...
i=4 (3): extend to 7,18 → steps[5]=3, steps[7]=3
i=5 (7): extend to 101,18 → steps[6]=4, steps[7]=4
max(steps) = 4
Complexity
- Time:
O(n²) - Space:
O(n)
Good first solution. Works for LeetCode’s constraints but is not optimal.
Java Solution — O(n²) Steps
class Solution {
public int lengthOfLIS(int[] nums) {
int n = nums.length;
int[] steps = new int[n];
Arrays.fill(steps, 1);
int max = 1;
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (nums[i] < nums[j])
steps[j] = Math.max(steps[j], steps[i] + 1);
}
max = Math.max(max, steps[i]);
}
return max;
}
}
Approach 2 — Patience Sorting + Binary Search: O(n log n)
Maintain a tails array where tails[i] is the smallest possible tail of any increasing subsequence of length i + 1 seen so far.
For each x:
- Binary-search
tailsfor the leftmost position with value>= x - If found → overwrite
tails[pos] = x(same length, better tail) - If not found → append
x(subsequence got longer)
Answer = tails.length.
Walkthrough
nums = [10, 9, 2, 5, 3, 7, 101, 18]
x=10 tails = [10]
x=9 tails = [9]
x=2 tails = [2]
x=5 tails = [2, 5]
x=3 tails = [2, 3]
x=7 tails = [2, 3, 7]
x=101 tails = [2, 3, 7, 101]
x=18 tails = [2, 3, 7, 18]
length = 4
tails is not the LIS itself
tails is sorted and its length is correct, but the array as a whole is not guaranteed to be a valid subsequence of nums in index order — each entry comes from a different underlying path.
Example: nums = [2, 6, 3, 4, 1, 5] → final tails = [1, 3, 4, 5], but 1 appears at index 4 while 3 and 4 appear earlier. You cannot pick [1, 3, 4, 5] left-to-right from the array.
Use this approach for length only. Reconstructing the actual sequence needs extra tracking (parent pointers or the O(n²) DP with backtracking).
Complexity
- Time:
O(n log n)— binary search per element - Space:
O(n)—tailsarray
Java Solution — O(n log n)
class Solution {
public int lengthOfLIS(int[] nums) {
List<Integer> tails = new ArrayList<>();
for (int x : nums) {
int pos = lowerBound(tails, x);
if (pos == tails.size())
tails.add(x);
else
tails.set(pos, x);
}
return tails.size();
}
// leftmost index where tails[i] >= x
private int lowerBound(List<Integer> tails, int x) {
int lo = 0, hi = tails.size();
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (tails.get(mid) >= x)
hi = mid;
else
lo = mid + 1;
}
return lo;
}
}
Why It Teaches You Something
The jump from O(n²) to O(n log n) is the lesson: stop storing the full answer at every position and instead store a sufficient invariant — the best tail for each length.
| Problem | What changes |
|---|---|
| Longest Increasing Subsequence (LC 300) | tails + binary search for length |
| Russian Doll Envelopes (LC 354) | sort by width, LIS on heights |
| Maximum Length of Pair Chain (LC 646) | sort by first, greedy / LIS variant |
| Number of Longest Increasing Subsequence (LC 673) | O(n²) DP with count tracking |
Mental model: patience sorting — each pile’s top is tails[i]; place each card on the leftmost pile with top >= x; number of piles = LIS length.