Home/Data Structures & Algorithms/Page 3

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.
Master core and advanced data structures and algorithms with production-grade implementations, hardware-conscious optimizations, and real-world system designs.
How to compute Running Median in continuous data streams with O(log N) per tick?
The classic, production-proven design for calculating running medians is the Dual-Heap Balancing Architecture (one Max-Heap and one Min-Heap). 1. The Mental Model Imagine splitting all numbers you've seen so far into two equal halves: The Lower Half (all numbers $le$ median): We store these in a MaxRead more
The classic, production-proven design for calculating running medians is the Dual-Heap Balancing Architecture (one Max-Heap and one Min-Heap).
1. The Mental Model
Imagine splitting all numbers you’ve seen so far into two equal halves:
The median is ALWAYS right at the fingertips: either the top of the Max-Heap, or the average of the two tops!
2. The Two Golden Invariants
To make this work 100% reliably, you must maintain two invariants after every single number is added:
max_heapmust be $le$ every element inmin_heap. (Ifmax_heap.top() > min_heap.top(), swap them).0 <= len(max_heap) - len(min_heap) <= 1.Production Python 3.12 Implementation
Performance & Production Benchmarks
- add_num() Time:
- find_median() Time:
- Space Complexity:
See lessO(log N). Pushing and popping from heaps of sizeN/2takes ~15-20 CPU instructions.O(1). Simply peek at heap roots (index 0). Instantaneous!O(N)total memory to store the incoming stream numbers.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.Why does the Two-Pointer approach beat Monotonic Stack for Trapping Rainwater in production?
I see this question come up all the time in engineering interviews and production optimizations. The short answer is: memory allocations and CPU cache locality. On paper, both the Monotonic Stack and Two Pointers are O(N) time. But in reality: Monotonic Stack: Pushes and pops indices into a dynamicRead more
I see this question come up all the time in engineering interviews and production optimizations. The short answer is: memory allocations and CPU cache locality.
On paper, both the Monotonic Stack and Two Pointers are
O(N)time. But in reality:std::stackin C++ or a dynamic slice in Python/Go). That means repeated memory allocations, pointer indirection, and cache misses every time the stack resizes or wanders through heap memory.left,right,left_max,right_max). These variables stay entirely inside CPU registers. There is zero heap allocation, zero pointer chasing, and the CPU prefetcher streams the array from both ends sequentially at full hardware bus speed.The Plain English Intuition
Think about standing at the edge of a swimming pool. The amount of water that can sit on top of any single column
iis strictly decided by one thing: the shorter of the two tallest walls on its left and right.Mathematically:
water[i] = max(0, min(max_left, max_right) - height[i]).Here is the genius of two pointers: you place one pointer at the start (
left) and one at the end (right). At every step:height[left] <= height[right], you know for certain that whatever tall wall exists on the far right is at least as tall asheight[left]. So the bottleneck for the left side is only determined byleft_max. You can safely calculate water atleftand moveleft++.height[right] < height[left], the exact opposite holds true. The bottleneck forrightis determined purely byright_max. You calculate water atrightand moveright--.You never have to look back, and you never have to store past heights in a stack!
Production-Ready Python 3.12 Implementation
Clean C++20 Version (Zero Allocations)
Complexity & Production Pitfalls
- Time Complexity:
- Space Complexity:
- 32-bit Integer Overflow: If you have an array of
See lessO(N). Every element is visited exactly once. No nested loops.O(1). No auxiliary memory allocated.100,000elements, each with height100,000, the total water can reach10^10. A standard 32-bit signed integer will overflow and return a negative number! Always use a 64-bit integer (int64_tin C++ orlongin Java) for the accumulator.How to rotate an array in-place with O(1) space and zero cache misses?
This is a classic problem where the textbook solution and the production solution diverge. When you are moving 10 million integers in memory, allocating a temp slice or doing naive cyclic swaps will kill your performance due to cache misses. The cleanest, most battle-tested way to do this in productRead more
This is a classic problem where the textbook solution and the production solution diverge. When you are moving 10 million integers in memory, allocating a temp slice or doing naive cyclic swaps will kill your performance due to cache misses.
The cleanest, most battle-tested way to do this in production is the 3-Reversal Trick (often called the Reversal Algorithm). It requires zero extra memory and traverses contiguous memory sequentially, which modern CPU prefetchers love.
1. The Intuition (Why 3 Reversals Work)
Suppose you have the array
[1, 2, 3, 4, 5, 6, 7]and you want to rotate right byk = 3(so[5, 6, 7, 1, 2, 3, 4]).Notice the split: the last
kelements need to move to the front, and the firstn - kelements move to the back. If you reverse the whole thing first, everything is in the right neighborhood but backwards:[7, 6, 5, 4, 3, 2, 1][5, 6, 7, 4, 3, 2, 1][5, 6, 7, 1, 2, 3, 4]Done! Every element is now in its exact final position.
2. Production C++20 Implementation
3. Python 3.12 Clean Version
4. Complexity Breakdown
O(N)total time. Step 1 doesn/2swaps, Step 2 doesk/2swaps, and Step 3 does(n-k)/2swaps. Total swaps = exactlynswaps. You can't beat linear time because every element must change position.O(1)auxiliary space. Just two index pointers living directly in CPU registers.5. Real-World Gotchas to Watch Out For
- When k > n: Always take
- Negative k (Left Rotation): If your system asks for a left rotation by
- Empty or Single Element Arrays: Check
See lessk = k % n. Forgetting this causes out-of-bounds pointer crashes whenk = 15on an array of length 5.k, simply transform it: a left rotation bykis equivalent to a right rotation by(n - (k % n)) % n.n <= 1upfront to prevent unsigned integer underflow onn - 1.