Card 07/ 09
The Idea Behind the O(n log n) Method for Increasing Subsequences
Keep the smallest possible tail for each run length, and a binary search finds where each value belongs.
The O(n log n) method finds the length of the longest increasing subsequence by keeping one list, tails, where tails[k] is the smallest value that can end an increasing subsequence of length k + 1, and placing each new value with a binary search. This card explains why that list stays sorted, traces it on the same eight numbers as Longest Increasing Subsequence in O(n²), and shows why its length is the answer but its contents may not be a subsequence at all.
A small tail is worth more than a large one, because more later values can extend it. So when a new value arrives, it either extends the longest run found until then, if it is larger than every tail, or it replaces the first tail that is not smaller than it, giving that run length a smaller ending. The position to replace is the lower bound of the value in tails, the first position holding a value at least as large, which Binary Search finds in O(log n) steps.
The trace below runs 5 2 8 6 3 6 9 7. The marker shows the position each value was placed at, by appending or by replacing.
Input5 2 8 6 3 6 9 7
5 appended
- 5pos
The tails list is empty, so 5 starts it: an increasing run of length 1 exists, ending at 5.
2 replaced
- 2pos
The lower bound of 2 is position 0, so 2 replaces 5. A run of length 1 that ends at 2 is easier to extend than one that ends at 5.
8 appended
- 2
- 8pos
8 is larger than every tail, so its lower bound is the end of the list and it is appended: a run of length 2 now exists, ending at 8.
6 replaced
- 2
- 6pos
The lower bound of 6 is position 1, so a run of length 2 can end at 6 instead of 8.
3 replaced
- 2
- 3pos
The lower bound of 3 is position 1 again. 2 then 3 is a run of length 2 that ends lower still.
6 appended
- 2
- 3
- 6pos
6 is larger than 3, the last tail, so it is appended: a run of length 3 now exists.
9 appended
- 2
- 3
- 6
- 9pos
9 is larger than every tail and is appended: length 4.
7 replaced
- 2
- 3
- 6
- 7pos
The lower bound of 7 is position 3, so 7 replaces 9 and the length stays 4. The list now reads 2 3 6 7, and its length, 4, is the answer.
ResultLength 4, the length of the tails list
The list stays sorted because a value replaces the first tail at least as large as itself, so it is still larger than the tail before it and no larger than the one it replaced. Sorted is what makes the binary search possible, and each of the n values costs one search, so the whole method is O(n log n).
The length of tails is the length of the longest increasing subsequence, 4, the same as the O(n²) method gave. Its contents are only a record of the best endings, and here 2 3 6 7 happens to be an increasing subsequence of the input. It need not be. On 3 4 1 the run ended with tails 1 4: the 1 replaced the 3 because a run of length 1 ending at 1 is the better start, but 1 comes after 4 in the input, so 1 4 is not a subsequence of it. The length, 2, is still right. Recovering an actual subsequence needs a parent array as in the O(n²) method, recording for each value the position of the tail before it.
- Time
- O(n log n), one binary search per value
- every case
- Space
- O(n) for tails
- worst case, when the whole input increases
- The answer
- The length of tails, not its contents
← → move through sections and cards