Dynamic Programming · 16 of 60
Longest Increasing Subsequence
Pick numbers from left to right, skipping any you like, to make the longest chain where every number is bigger than the one before.
- Simple way
- time O(n^2)
room O(n) - Clever way
- time O(n log n)
room O(n)
The problem, simply
You get a row of numbers. Choose some of them, keeping their order, so that each chosen number is strictly bigger than the one before it. You may skip as many as you like. How many can you choose at most?
The chosen numbers are an increasing subsequence. Give back the length of the longest one.
A story to remember it
Picture it
Eight numbers. Picking 2, 5, 7, 101 (skipping the rest) gives an increasing chain of length 4. No chain of length 5 exists.
- answer
- ignored
Examples
A normal case
- In
arr = [10, 9, 2, 5, 3, 7, 101, 18]- Out
4
2, 5, 7, 101 works. So do 2, 3, 7, 18 and a few others. The length is what matters.
Ups and downs
- In
arr = [0, 1, 0, 3, 2, 3]- Out
4
0, 1, 2, 3.
All the same
- In
arr = [7, 7, 7, 7]- Out
1
Strictly bigger is required, so equal numbers cannot follow each other.
Going down
- In
arr = [5, 4, 3, 2, 1]- Out
1
Every number is smaller than the one before. Any single number is a chain of length 1.
Empty
- In
arr = []- Out
0
No numbers, no chain.
Way 1: the simple way
The plain recursion tries "take or skip" for every number, which is O(2^n). Most of those calls repeat, so store a smaller answer instead: dp[i] is the longest chain that ends exactly at arr[i].
- Every number alone is a chain, so
dp[i]starts at 1. - For each
i, look back at everyj < i. Ifarr[j] < arr[i], the chain ending atjcan be extended byarr[i]:dp[i] = max(dp[i], dp[j] + 1). - The answer is the biggest
dp[i].
The look back table. dp[i] is the longest chain that ends at arr[i]. For 7 (index 5), the smaller numbers before it are 2, 5 and 3, with dp values 1, 2 and 2, so dp[5] = 2 + 1 = 3.
- looking now
- checking
- answer
class Solution {
public int lengthOfLIS(int[] arr) {
int n = arr.length, best = 0;
int[] dp = new int[n];
for (int i = 0; i < n; i++) {
dp[i] = 1; // the number alone
for (int j = 0; j < i; j++) {
if (arr[j] < arr[i]) {
dp[i] = Math.max(dp[i], dp[j] + 1); // extend the chain at j
}
}
best = Math.max(best, dp[i]);
}
return best;
}
}How long it takes: every number looks back at all the numbers before it, O(n^2). Extra room: the dp array, O(n). This version is easy to change for "print the chain" or "count the chains", so learn it too.
Way 2: the clever way
Keep the pile tops from the story in an array tails. tails[k] is the smallest number that can end an increasing chain of length k + 1. The tops always go up from left to right, so you can binary search them.
- For each number
x, find the first slot intailswhose value isxor more. - If there is no such slot,
xis bigger than every top: add it at the end. The longest chain grew by one. - Otherwise put
xin that slot. A chain of that length can now end with a smaller number. - The answer is the size of
tails.
Keep the smallest tail for every chain length
01 / 10
tails[k] will hold the smallest possible last number of an increasing chain of length k + 1. It starts empty.
- length
- 0
- looking now
- answer
- just added
class Solution {
public int lengthOfLIS(int[] arr) {
int[] tails = new int[arr.length];
int size = 0;
for (int x : arr) {
int lo = 0, hi = size; // search in tails[0..size)
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
if (tails[mid] < x) lo = mid + 1;
else hi = mid;
}
tails[lo] = x; // replace, or add at the end
if (lo == size) size++;
}
return size;
}
}How long it takes: one binary search per number, O(n log n). Extra room: the tails array, O(n).
Watch out for
- Strictly increasing needs "first slot that is
>= x" (lower bound). If equal numbers are allowed in the chain (non-decreasing), search for the first slot that is> x(upper bound) instead. - In Way 1 the answer is the biggest
dp[i], notdp[n - 1]. The best chain does not have to end at the last number. - Empty input: both ways return 0.
What interviewers ask next
- Print one longest chain: in Way 1, keep
parent[i]= thejthat gave the bestdp[i], then walk back from the best end. In Way 2, store the index in each slot and a parent link for each number. - Count how many longest chains there are: Way 1 with a second array
count[i], adding counts whendp[j] + 1ties the best. - Russian doll envelopes: sort by width going up and by height going down for equal widths, then run Way 2 on the heights.
- Fewest numbers to delete so the row is increasing:
n - LIS. - Longest common subsequence of two orderings of the same distinct numbers: relabel one by positions in the other, then LIS solves it in
O(n log n).
Remember this
Grown-up words: the chain is the longest increasing subsequence (LIS), the card piles are patience sorting, tails is the array of smallest tail values, the slot search is a lower bound binary search, and dp[i] in Way 1 is the "ends here" DP state.