Longest Increasing Subsequence faster
Picture
For values 3,5,2, a length-one subsequence ending at 2 is more useful than one ending at 3.
Predict
Does the tails array always equal an actual subsequence?
Walk through the small example
No. For each achievable length, only the smallest ending value matters for future extensions.
Mental model and complete Python solution
Many subsequences of equal length compete. Keep only the one with the smallest tail because it is never harder to extend. Tails across lengths are ordered, enabling binary search.
What the function promises: Return the length of a strictly increasing subsequence. Empty input returns zero.
def lis_length(values):
tails = []
for value in values:
left, right = 0, len(tails)
while left < right:
middle = (left+right)//2
if tails[middle] >= value: right = middle
else: left = middle+1
if left == len(tails): tails.append(value)
else: tails[left] = value
return len(tails)
assert lis_length([10,9,2,5,3,7,101,18]) == 4
assert lis_length([2,2,2]) == 1
Work and storage: O(n log n) time and O(n) space.
When to recognize it: For each achievable length, only the smallest ending value matters for future extensions.
Check before applying it: Using upper bound instead of lower bound changes strict increasing into nondecreasing behavior.
Change one rule
Return one actual LIS.
See what changes
Store predecessor indices and which input index currently realizes each tails length.
Connect
subsequences: Compress the quadratic predecessor scan into best tails.
Binary search: find a boundary: Find the first tail that cannot stay before the new value.