Introduction
The Longest Increasing Subsequence (LIS) problem asks for the length of the longest subsequence where each element is strictly greater than the previous one.
Example: For array :
- is an increasing subsequence of length 4
- is an increasing subsequence of length 2
- The LIS has length 4
Applications:
- Patience sorting algorithm
- Box stacking problems
- Longest chain problems in DAGs
- Version control (finding longest common history)
DP Approach (O(n²))
Define as the length of the LIS ending at index .
Recurrence:
If no valid exists, (the element itself forms an LIS of length 1).
Algorithm:
function lis_dp(a):
n = length(a)
dp = array of size n, all initialized to 1
for i from 1 to n-1:
for j from 0 to i-1:
if a[j] < a[i]:
dp[i] = max(dp[i], dp[j] + 1)
return max(dp)
Time: , Space:
Binary Search Approach (O(n log n))
Maintain an array where stores the smallest tail element for all increasing subsequences of length .
Key insight: is always sorted, so we can use binary search.
Algorithm:
function lis_binary(a):
tails = empty array
for each num in a:
pos = binary_search(tails, num) // find first >= num
if pos == length(tails):
tails.append(num)
else:
tails[pos] = num
return length(tails)
Why it works: By keeping the smallest possible tail for each length, we maximize the chance of extending the subsequence.
Time: , Space:
Trace Through Example
Array:
Using the binary search approach:
| Step | num | tails (before) | Action | tails (after) |
|---|---|---|---|---|
| 1 | 10 | [] | append | [10] |
| 2 | 9 | [10] | replace at 0 | [9] |
| 3 | 2 | [9] | replace at 0 | [2] |
| 4 | 5 | [2] | append | [2, 5] |
| 5 | 3 | [2, 5] | replace at 1 | [2, 3] |
| 6 | 7 | [2, 3] | append | [2, 3, 7] |
| 7 | 101 | [2, 3, 7] | append | [2, 3, 7, 101] |
| 8 | 18 | [2, 3, 7, 101] | replace at 3 | [2, 3, 7, 18] |
Final length: 4
Note: is NOT the actual LIS. It just tracks the optimal tails.