The O(n) Club: Russian Doll Envelopes—Where Sorting Actually Makes You Look Smart
⚡ TL;DR
Find the most envelopes that fit inside each other, Russian-doll style—size must strictly increase in both width and height. Most people go brute force and get O(n²) runtime migraines. The pros use a sort-and-LIS trick and slip out before anyone notices. Here’s the flavor of the wrong way:
// Don’t even think about copy-pasting int maxChain = 1; for (int i = 0; i < n; ++i) for (int j = 0; j < i; ++j) if (env[j].width < env[i].width && env[j].height < env[i].height) maxChain = Math.max(maxChain, dp[j] + 1);
🧠 How Devs Usually Mess This Up
So, you sorted by width. Great. But when widths tie, you sorted height ascending? Get ready for envelopes nesting like Russian matryoshka clones—wrong! Forget about that strictly greater thing? Now you’re letting identical envelopes morph into a single envelope hydra. And if you tackled this with only DP, you probably saw your runtime stats weep uncontrollably. Sorting details and edge cases: ignore at your own peril.
🔍 What’s Actually Going On
This isn’t just a 2D LIS problem—it’s the sneaky cousin that needs a sorting Jedi move. By sorting envelopes by width ascending but height descending (for ties), you make sure equal widths don’t chain up, letting you safely apply classical LIS—in just one dimension. It’s algorithmic origami: fold once, then let LIS do the pretty part.
🛠️ PseudoCode
- Step 1: Sort envelopes
// Width ascending; if equal, height descending Arrays.sort(envelopes, (a, b) -> { if (a[0] == b[0]) return b[1] - a[1]; return a[0] - b[0]; });
Why height descending? Keeps envelopes of the same width from forming an illegal queue.
- Step 2: Extract heights
int[] heights = new int[envelopes.length]; for (int i = 0; i < envelopes.length; ++i) heights[i] = envelopes[i][1];
Heights now represent your nesting potential. Live your best LIS life.
- Step 3: Run LIS on heights (Patience Sorting Style)
// Find LIS (longest increasing sequence) of heights List <integer> piles = new ArrayList<>(); for (int h : heights) { int idx = Collections.binarySearch(piles, h); if (idx < 0) idx = -(idx + 1); if (idx == piles.size()) piles.add(h); else piles.set(idx, h); } // piles.size() is your answer </integer>
Less patience, more results. O(n log n) and interviewers will nod approvingly.
💻 The Code
import java.util.*;
public class RussianDollEnvelopes {
public int maxEnvelopes(int[][] envelopes) {
if (envelopes == null || envelopes.length == 0) return 0;
Arrays.sort(envelopes, (a, b) -> {
if (a[0] == b[0]) return b[1] - a[1];
return a[0] - b[0];
});
int[] heights = new int[envelopes.length];
for (int i = 0; i < envelopes.length; ++i) {
heights[i] = envelopes[i][1];
}
List
<integer> piles = new ArrayList<>();
for (int h : heights) {
int idx = Collections.binarySearch(piles, h);
if (idx < 0) idx = -(idx + 1);
if (idx == piles.size()) piles.add(h);
else piles.set(idx, h);
}
return piles.size();
}
}
</integer>
⚠️ Pitfalls, Traps & Runtime Smacks
- Sorting heights ascending for ties? Enjoy demoing a wrong answer—you’ll get chains of same-width envelopes if you do!
- Forgetting ‘strictly greater’? Your answer will balloon with illegitimate envelopes. Test cases will not be kind.
- All identical envelopes? Your solution should return 1. Don’t overthink; just smile and return 1.
- Time/Space Complexity: O(n log n) time (because LIS with binary search) and O(n) space. Your future self thanks you for not doing O(n²).
🧠 Interviewer Brain-Tattoo
“Sort with brains. LIS with style. Descending tie-breakers: because Russian dolls shouldn’t be twins.”