Intuition
Define dp[i] = length of the longest increasing subsequence that ends at index i. Why end-at? Because then any valid subsequence ending at i must extend some shorter subsequence ending at some earlier index j where nums[j] < nums[i]. The recurrence falls out: dp[i] = 1 + max(dp[j] for j < i if nums[j] < nums[i]), with 1 as the default if no such j exists.
Explanation
Why "ending at i" is the right state
If we instead defined dp[i] as "length of LIS in nums[0..i]", we'd lose track of which element ended the subsequence — and that matters when extending to i+1. Anchoring on "the subsequence ends here" makes the transition local: we only need dp[j] for earlier j whose value is smaller.
Steps
- If
numsis empty, return0. - Initialise
dp = [1] * n— every index, by itself, is a length-1 increasing subsequence. - For each
ifrom1ton - 1:- For each
jfrom0toi - 1:- If
nums[j] < nums[i], setdp[i] = max(dp[i], dp[j] + 1).
- If
- For each
- Return
max(dp)— the answer can end at any index, so we take the global max.
Walk-through
nums = [10, 9, 2, 5, 3, 7, 101, 18].
| i | nums[i] | candidates dp[j]+1 (for j<i, nums[j]<nums[i]) | dp[i] |
|---|---|---|---|
| 0 | 10 | none | 1 |
| 1 | 9 | none (10 not < 9) | 1 |
| 2 | 2 | none | 1 |
| 3 | 5 | dp[2]+1 = 2 (since 2 < 5) | 2 |
| 4 | 3 | dp[2]+1 = 2 (since 2 < 3) | 2 |
| 5 | 7 | dp[2]+1=2, dp[3]+1=3, dp[4]+1=3 | 3 |
| 6 | 101 | every previous element is < 101 → max(dp)+1 = 4 | 4 |
| 7 | 18 | dp[2]+1=2, dp[3]+1=3, dp[4]+1=3, dp[5]+1=4 | 4 |
max(dp) = 4. ✓
The actual LIS realising length 4: [2, 3, 7, 101] or [2, 3, 7, 18] etc.
Edge cases
- Empty list: return
0. - Single element:
dp = [1], return1. - All decreasing: every
dp[i] = 1, answer is1.
Complexity
- Time:
O(n²)— the nested loop. - Space:
O(n)— the dp array.
Implementation
class Solution {
public int lengthOfLIS(int[] nums) {
if (nums.length == 0) return 0;
int[] dp = new int[nums.length];
java.util.Arrays.fill(dp, 1);
for (int i = 1; i < nums.length; i++) {
for (int j = 0; j < i; j++) {
if (nums[j] < nums[i]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
}
int max = 0;
for (int v : dp) max = Math.max(max, v);
return max;
}
}