
Lost your password? Please enter your email address. You will receive a link and will create a new password via email.
Please briefly explain why you feel this question should be reported.
Please briefly explain why you feel this answer should be reported.
Please briefly explain why you feel this user should be reported.
Join us in connecting knowledge with those who need it. Share your expertise, discover new perspectives, and help build a smarter, more connected world.
Create A New Account
Why does Patience Sorting solve Longest Increasing Subsequence in O(N log N) instead of O(N^2)?
This is one of the most common points of confusion when studying LIS. Let's clear up the mystery of why the tails array works even though its contents look 'wrong'. 1. What does the `tails` array actually represent? In Patience Sorting (inspired by solitaire card games): tails[k] stores the SMALLESTRead more
This is one of the most common points of confusion when studying LIS. Let’s clear up the mystery of why the
tailsarray works even though its contents look ‘wrong’.1. What does the `tails` array actually represent?
In Patience Sorting (inspired by solitaire card games):
tails[k]stores the SMALLEST ending value of an increasing subsequence of lengthk + 1found so far.Why do we care about the smallest ending value? Because in an increasing subsequence, the smaller the number you end with, the easier it is for future numbers to be bigger than it! You want to keep your options as open as possible.
2. The Step-by-Step Card Dealing Analogy
Suppose our array is:
[10, 9, 2, 5, 3, 7, 101, 18].10:tails = [10](Best subsequence of len 1 ends with 10).9: 9 < 10. Replace 10 with 9:tails = [9](Ending with 9 is strictly better than ending with 10).2: 2 < 9. Replace 9 with 2:tails = [2].5: 5 > 2! Extend!tails = [2, 5](Best len 1 ends in 2, best len 2 ends in 5).3: 3 < 5. Replace 5 with 3:tails = [2, 3](Now best len 2 ends in 3!).7: 7 > 3! Extend!tails = [2, 3, 7](Len 3).101: Extend!tails = [2, 3, 7, 101](Len 4).18: 18 < 101. Replace 101 with 18:tails = [2, 3, 7, 18].Total length of
tailsis 4. The answer is 4!3. Why the array contents might look scrambled, but length is ALWAYS correct
Imagine if after
[2, 3, 7, 18]we saw1. We would replace2with1, resulting intails = [1, 3, 7, 18].Notice that
[1, 3, 7, 18]might not be a valid subsequence from the original array. And that doesn’t matter!Replacing
2with1only prepares the board for a hypothetical future subsequence that starts with1. It does not change the fact that a valid subsequence of length 4 ([2, 3, 7, 18]) was already locked in!The length of
tailsonly increases when a number is strictly greater than ALL existing tail values. Replacements never shrink the array length!Clean Python 3.12 Implementation with bisect_left
Complexity Breakdown
- Time Complexity:
- Space Complexity:
See lessO(N log N). We iterate throughNelements, and for each element we perform binary search overtails(at most lengthN).N * log(N). For100,000elements, this finishes in 0.02 seconds (compared to ~45 seconds forO(N^2)).O(N)to hold thetailsarray.0/1 Knapsack: Why does reverse iteration turn O(N*W) space into O(W) space?
This is one of the most fundamental 'aha!' moments in dynamic programming. Let's walk through the memory mechanics so you never forget it. 1. The 2D State Transition In the classic 0/1 Knapsack, the formula is: dp[i][w] = max( dp[i-1][w], // Option A: Skip item i (take answer from previous row) dp[iRead more
This is one of the most fundamental ‘aha!’ moments in dynamic programming. Let’s walk through the memory mechanics so you never forget it.
1. The 2D State Transition
In the classic 0/1 Knapsack, the formula is:
Notice the critical detail: in Option B,
dp[i-1][w - weight[i]]comes from rowi-1(before itemiwas even considered). That is what guarantees you only take itemiat most once.2. Compressing to a 1D Array
Notice that to compute row
i, you only ever look at rowi-1. You don’t need rowsi-2,i-3, etc. So we can just reuse a single 1D array:dp[w].What happens if you iterate FORWARD (w = weight[i] to W)?
Suppose item 1 has
weight = 2, value = 10and capacity is6.w = 2:dp[2] = dp[0] + 10 = 10.w = 4:dp[4] = dp[4 - 2] + 10 = dp[2] + 10 = 10 + 10 = 20! (Wait, you just reused item 1 twice!)w = 6:dp[6] = dp[4] + 10 = 30! (You used item 1 three times!)Because you updated smaller weights first, larger weights read the already updated values from the current item. That turns it into Unbounded Knapsack (infinite items)!
What happens if you iterate BACKWARD (w = W down to weight[i])?
w = 6: readsdp[4](which is still 0 from the previous item!).dp[6] = 0 + 10 = 10.w = 4: readsdp[2](which is still 0!).dp[4] = 0 + 10 = 10.w = 2: readsdp[0](which is 0!).dp[2] = 0 + 10 = 10.By sweeping backwards, whenever you query
w - weight[i], that smaller index has not yet been touched for the current item. It still holds the pristine value from itemi-1!Production Python 3.12 Implementation
Summary Rule of Thumb
- 0/1 Knapsack (items used at most once) → Iterate Backward (
- Unbounded Knapsack / Coin Change (items can be reused infinitely) → Iterate Forward (
See lessW → weight).weight → W).How to traverse a Binary Tree in O(1) memory without recursion or stack (Morris Traversal)?
Morris Traversal is one of the most brilliant algorithms in computer science. It solves the exact constraint you're facing: how do you traverse a tree without spending any extra memory on a stack? 1. The Core Secret: Threaded Binary Trees When you are at a node and go deep into its left subtree, howRead more
Morris Traversal is one of the most brilliant algorithms in computer science. It solves the exact constraint you’re facing: how do you traverse a tree without spending any extra memory on a stack?
1. The Core Secret: Threaded Binary Trees
When you are at a node and go deep into its left subtree, how do you get back up to the node without a parent pointer or call stack? Normally, you need a stack to remember the return path.
J. H. Morris realized something clever: in every binary tree, about half of all pointers are NULL! Every leaf node has a
nullright child that is sitting there doing nothing.Morris repurposes these unused
nullpointers as temporary bridge wires (called “threads”) back to the inorder successor:null, point it back to the current node:predecessor->right = current. Then movecurrent = current->left.current, that means you have already finished visiting the left subtree! You print/recordcurrent->val, restore the pointer tonull(repairing the tree), and movecurrent = current->right!When the algorithm finishes, the tree is 100% restored to its original state. Zero memory allocated, zero permanent mutations!
Clean C++20 Morris Inorder Traversal
Complexity & Trade-offs
- Time Complexity:
- Space Complexity:
- Thread-Safety Warning: Because Morris Traversal temporarily mutates right pointers during execution, it is not safe for concurrent readers on the same tree instance. If multiple threads read the tree simultaneously, use standard recursive DFS with a large stack or an explicit thread-local queue.
See lessO(N). Even though we search for predecessors, each edge in the tree is traversed at most 3 times (once to find predecessor, once to create thread, once to remove thread).3 * (N - 1) = O(N).O(1)auxiliary space. Just two pointers (currandpred). No call stack, no heap allocations.Why does standard std::priority_queue in Dijkstra cause memory bloating, and how to fix it?
You have hit on one of the most critical differences between competitive programming hacks and real-world systems engineering. In standard textbook Dijkstra, because std::priority_queue does not support a native decrease_key() operation, engineers take the lazy route: they just push duplicate entrieRead more
You have hit on one of the most critical differences between competitive programming hacks and real-world systems engineering.
In standard textbook Dijkstra, because
std::priority_queuedoes not support a nativedecrease_key()operation, engineers take the lazy route: they just push duplicate entries into the heap and skip stale ones withif (d > dist[u]) continue;. This is called Lazy Deletion Dijkstra.While lazy Dijkstra works fine on small graphs, on dense graphs with 10 million edges, your heap stores up to 10 million items instead of 1 million nodes, blowing through your CPU’s L3 cache.
The Solution: Indexed Priority Queue
An Indexed Binary Heap (Indexed Priority Queue) maintains an internal inverse lookup array (
pos[u]) that tracks the exact heap index of every nodeu.When a shorter path to node
uis discovered:uis already in the heap, you don’t insert a duplicate—you calldecrease_key(u, new_dist), which directly updates the value in-place and sifts it up inO(log V)time!High-Performance C++20 Indexed Min-Heap Dijkstra
Performance Benchmark Comparison
By enforcing an explicit upper bound of
See lessVelements in the heap, the entire indexed heap fits cleanly inside modern CPU L2/L3 caches, drastically accelerating routing throughput!Binary Search on Answer: How to solve Koko Eating Bananas without floating-point bugs?
Binary Search on Answer Space is one of the highest-leverage algorithmic patterns you can learn. Once you recognize it, dozens of seemingly hard problems (shipping packages, splitting arrays, cutting ribbons, allocating memory) all collapse into the exact same 15 lines of code. 1. When Can You Use TRead more
Binary Search on Answer Space is one of the highest-leverage algorithmic patterns you can learn. Once you recognize it, dozens of seemingly hard problems (shipping packages, splitting arrays, cutting ribbons, allocating memory) all collapse into the exact same 15 lines of code.
1. When Can You Use This Pattern?
Ask yourself one simple question: Is the condition monotonic?
k = 100bananas/hour and succeeds in finishing in underhhours, would eating at speedk = 101also succeed? Yes, always.k = 5is too slow and fails, would eating at speedk = 4also fail? Yes, always.Because the outcome transitions cleanly from
False, False, ..., True, True, True, the answer space is monotonic. That means we don’t need to test every speed from 1 to 1 billion linearly—we can binary search it inO(log(MaxPile))steps!2. The Integer Ceiling Trick (Say Goodbye to Float Bugs)
If Koko has a pile of
7bananas and eats at speedk = 3, she needsceil(7 / 3) = 3hours.In Python or C++, doing
math.ceil(pile / k)converts the numbers to IEEE-754 64-bit floats. On massive numbers (e.g.10^14), floating-point precision degrades, causing silent off-by-one errors.The standard integer arithmetic replacement for
ceil(a / b)is:Let’s test it:
(7 + 3 - 1) // 3 = 9 // 3 = 3. Exactly right, 100% integer math, zero float conversions!3. Clean Python 3.12 Implementation
4. Complexity & Production Benchmarks
- Time Complexity:
- Space Complexity:
- Overflow Note for C++ / Java: In C++,
See lessO(N * log(M))whereNis the number of piles andMismax(piles). IfM = 10^9,log2(10^9) ≈ 30. Even with 100,000 piles, the validation function runs at most 30 times. Total operations: ~3 million, executing in under 15 milliseconds.O(1)auxiliary memory.total_hourscan easily exceed2^31 - 1if speeds are small and piles are large. Always declareint64_t total_hours = 0;to prevent integer overflow.How does a Monotonic Stack solve Largest Rectangle in Histogram in a single pass?
The Largest Rectangle in Histogram is famous because it feels like magic until you see the visual geometry behind it. Let's demystify it once and for all. 1. The Core Realization For any bar at index k with height H = heights[k], what is the widest rectangle you can make using H as the height? The rRead more
The Largest Rectangle in Histogram is famous because it feels like magic until you see the visual geometry behind it. Let’s demystify it once and for all.
1. The Core Realization
For any bar at index
kwith heightH = heights[k], what is the widest rectangle you can make usingHas the height?The rectangle can extend as far left as possible until it hits a bar shorter than
H, and as far right as possible until it hits another bar shorter thanH.So the entire problem boils down to finding two things for every bar:
2. Why a Monotonic Increasing Stack?
A monotonic stack keeps indices of bars whose heights are strictly increasing:
[2, 4, 6, 8].As long as the next bar is taller or equal, the rectangle could potentially keep growing, so we just push its index onto the stack.
The Trigger: The moment you encounter a bar that is shorter than the top of the stack (say we see a bar of height
3when the stack top is8), you have found the Right Boundary for that8! The bar of height8cannot extend any further to the right. Its journey is finished.When you pop
8:iis its first shorter bar on the right.Therefore, the width of the rectangle bounded by height
His simply:width = (i - stack[-1] - 1).3. Clean Python 3.12 Implementation with Sentinel Trick
4. Step-by-Step Trace with Numbers
Let’s trace
heights = [2, 1, 5, 6, 2, 3]with sentinel[2, 1, 5, 6, 2, 3, 0]:i = 0 (h=2): Stack =[0]i = 1 (h=1): 1 < 2! Pop0(h=2). Stack empty → width = 1. Area =2 * 1 = 2. Push 1. Stack =[1].i = 2 (h=5): 5 > 1. Push 2. Stack =[1, 2].i = 3 (h=6): 6 > 5. Push 3. Stack =[1, 2, 3].i = 4 (h=2): 2 < 6!3(h=6): right = 4, left = 2 → width =4 - 2 - 1 = 1. Area =6 * 1 = 6.2(h=5): right = 4, left = 1 → width =4 - 1 - 1 = 2. Area =5 * 2 = 10!Push 4. Stack =
[1, 4].0sentinel cleanly flushes all remaining elements.Max Area = 10 (from bars of height 5 and 6).
5. Why is this strictly O(N)?
Even though there is a
See lesswhileloop inside theforloop, every index is pushed onto the stack exactly once and popped from the stack at most once. Total operations across the entire array are at most2N. That is a rock-solid, linearO(N)runtime withO(N)memory.How to design a thread-safe LRU Cache in O(1) without memory leaks?
Building an LRU cache from scratch is one of the best ways to understand how data structures combine in real systems. The industry standard pattern is combining two complementary structures: A Hash Map: Gives you O(1) key-to-node lookups. A Doubly Linked List (DLL) with Dummy Head & Tail: GivesRead more
Building an LRU cache from scratch is one of the best ways to understand how data structures combine in real systems. The industry standard pattern is combining two complementary structures:
O(1)key-to-node lookups.O(1)node insertion at the front (most recent) andO(1)node removal from the back (least recent).The secret trick that eliminates 90% of bug-prone null checks is using sentinel (dummy) head and tail nodes. Instead of constantly checking
if (head == null)orif (node->prev == null), the dummy head and tail are always linked together:head <-> tail. Any real data node always lives safely in between them!Architectural Diagram
Clean, Idiomatic Python 3.12 Implementation
Why Storing the Key Inside the Node is Crucial
Notice that the
Nodeclass stores bothkeyandval. Many developers forget to storekeyin the node and only storeval. But when the cache reaches full capacity and you evicttail.prev, how do you delete that entry from the hash map? Withoutnode.key, you’d have to search the entire hash map inO(N)time, destroying yourO(1)guarantee!Thread Safety in Production
If multiple threads access this cache concurrently:
- In Python, use
- In Go or C++, a Read-Write Lock (
See lessthreading.Lock()aroundgetandput.sync.RWMutex/std::shared_mutex) is often tempting, but remember: even aget()operation mutates the linked list (to move the accessed item to the front)! Therefore, standard read locks are not enough—you must acquire an exclusive lock or use lock striping across multiple shards.