CMD Guide
HomeDSADynamic Programming

Longest Increasing Subsequence

Longest Increasing Subsequence (LIS) works by tracking, for every possible subsequence length, the smallest tail value that can end a subsequence of that length — because a smaller tail always leaves more room to extend later, so you never need to remember more than one candidate per length.

Recognize the pattern

Brute force → optimal

ApproachIdeaTimeSpace
Recursion over subsetsInclude or skip each index; check increasing constraintO(2^n)O(n) stack
DP over pairsdp[i] = LIS length ending at i; scan all j<iO(n^2)O(n)
Patience sorting + binary searchtails[] = smallest tail seen per length; binary-search the insertion pointO(n log n)O(n)

Complexity, derived

O(n^2) DP. For each index i we scan all earlier indices j < i to test nums[j] < nums[i] and relax dp[i] = max(dp[i], dp[j]+1). Total comparisons = 0+1+2+...+(n-1) = n(n-1)/2 → O(n^2) time. One dp[] array of size n → O(n) space.

O(n log n) patience sorting. tails[] holds at most one value per achievable length, so its size is bounded by n. Each of the n elements does exactly one binary search over tails (size ≤ n) → n × O(log n) = O(n log n) time, O(n) space for tails[].

Worked example

nums = [10, 9, 2, 5, 3, 7, 101, 18]. Track tails[] (smallest tail per length) as we scan left to right, using binary search to find where each value belongs.

ReadActiontails after
10tails empty → append[10]
99 < 10 → replaces 10 (first tail ≥ 9)[9]
22 < 9 → replaces 9[2]
55 > all → append[2, 5]
33 < 5 → replaces 5[2, 3]
77 > all → append[2, 3, 7]
101101 > all → append[2, 3, 7, 101]
1818 < 101 → replaces 101[2, 3, 7, 18]

Final length = len(tails) = 4. Note tails = [2, 3, 7, 18] is a valid LIS here, but that's coincidental — tails is a bookkeeping array of best-known tail values, not guaranteed to be a real subsequence that occurred in that exact order (see Pitfalls).

Java (O(n log n))

int lengthOfLIS(int[] nums) {
    int[] tails = new int[nums.length];
    int size = 0;
    for (int x : nums) {
        int lo = 0, hi = size; // binary search for first tails[i] >= x
        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;
}

Pitfalls

When to use / when not

Use patience sorting O(n log n) whenever n is large (thousands+) or you only need the length. Use the O(n^2) DP when n is small, you need the actual subsequence with simple bookkeeping, or the comparison condition is not a simple total order (e.g. LIS with additional constraints per element) where binary search doesn't directly apply.

Vs. LCS-based reduction: LIS can also be solved by sorting a copy, then running LCS(original, sorted-deduped) in O(n^2) or O(n log n) with coordinate compression + Fenwick tree — useful when the problem is stated as "LIS of one sequence relative to another's order" (e.g. minimum swaps problems). That reduction adds implementation complexity for no asymptotic win over patience sorting on a single sequence, so prefer patience sorting unless the two-sequence framing is natural.

Takeaways

Recall: Why does replacing tails[k] with a smaller value never shrink the final answer's correctness, even though tails is not itself a valid subsequence?


Sources: CLRS-style patience sorting analysis; LeetCode 300 (Longest Increasing Subsequence) constraints and examples.

🤖 Don't fully get this? Learn it with Claude

Stuck on Longest Increasing Subsequence? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Longest Increasing Subsequence** (DSA) and want to truly understand it. Explain Longest Increasing Subsequence from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Longest Increasing Subsequence** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Longest Increasing Subsequence** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Longest Increasing Subsequence** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes