Longest Increasing Subsequence
The drill: Find the length of the longest run of values you can pick out of an array, keeping their original order, such that each pick is strictly bigger than the one before — skipping is free, ties break the run.
An array of integers arrives, and the goal is to pick out as long a chain of its values as possible — moving left to right, keeping their original order — where every picked value is strictly bigger than the one picked right before it.
Skipping values costs nothing; the chain doesn't need to be a contiguous run of the array, just values that appear somewhere in it in the same relative order they started in. Equal values back to back can never both belong to the same chain, since the rule demands strictly bigger, not bigger-or-equal.
Only the length of the longest such chain is asked for, not the chain's actual contents.
- arrays can be empty, and an empty array's answer is zero
- values may repeat, be negative, zero, or positive
- strictly increasing means ties break the chain
- only the length is required, not the subsequence itself
HINT 1 THE NUDGE
Trying every subsequence is exponential. But the best run ending exactly at some index only cares about the best runs ending at smaller earlier values — what state would let you build up from there?
HINT 2 THE STRUCTURE
dp[i] = the longest increasing run that ends at index i. It's one plus the best dp[j] among every earlier index whose value is smaller than nums[i]; the answer is the largest dp anywhere.
HINT 3 ONE STEP FROM THE ANSWER
That still rescans everyone before each index. Instead keep the smallest tail value achievable for each run length seen so far, and binary-search where the new value belongs — it either extends the longest run or cheapens an earlier length.
Keep tails: the smallest ending value reachable for each run length so far. Binary-search each new value in.
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
tails = [] # tails[k] = smallest possible tail of a run of length k + 1
for x in nums:
i = bisect.bisect_left(tails, x)
if i == len(tails):
tails.append(x)
else:
tails[i] = x
return len(tails)class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
n = len(nums)
dp = [1] * n # dp[i] = longest increasing run ending exactly at i
for i in range(n):
for j in range(i):
if nums[j] < nums[i]:
dp[i] = max(dp[i], dp[j] + 1)
return max(dp) if dp else 0class Solution {
public int lengthOfLIS(int[] nums) {
int[] tails = new int[nums.length];
int size = 0;
for (int x : nums) {
int lo = 0, hi = size;
while (lo < hi) {
int mid = (lo + hi) / 2;
if (tails[mid] < x) {
lo = mid + 1;
} else {
hi = mid;
}
}
tails[lo] = x;
if (lo == size) {
size++;
}
}
return size;
}
}class Solution {
public int lengthOfLIS(int[] nums) {
int n = nums.length;
int[] dp = new int[n];
Arrays.fill(dp, 1);
int best = n == 0 ? 0 : 1;
for (int i = 0; i < n; i++) {
for (int j = 0; j < i; j++) {
if (nums[j] < nums[i]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
best = Math.max(best, dp[i]);
}
return best;
}
}✓ CHIP-TIMED — ALL 4 SOLUTIONS RAN GREEN AGAINST SELF-AUTHORED CASES IN CI · JDK 21 · CPYTHON 3.12 · NOTHING PUBLISHES RED