Hi👋SpeedAlgo • Deliberate Practice & Cognitive Ergonomics for Software Engineers

An interactive algorithm mastery and technical interview preparation platform published by Hi👋WebEnterprise. Built for senior and staff software engineers preparing for rigorous coding screens at top tech companies (FAANG/MAMAA).

13 Core Algorithmic Patterns & 225 Practice Problems

  • 1. Two Pointers (16 Paradigms, 43 Problems): Opposite ends, sorted pair sums, palindrome verification, trapping rain water, 3Sum, container with most water — includes the Sliding Window and Fast & Slow Pointers paradigms (contiguous subarray invariants, longest substrings, Floyd's cycle detection, linked list middle).
  • 2. Binary Search (9 Paradigms, 14 Problems): Monotonic predicate partitioning (feasible(x)), boundary search, rotated sorted arrays, square roots, minimum ship capacity, median of two sorted arrays, matrix median on value range.
  • 3. Bit Manipulation (5 Paradigms, 11 Problems): Counting set bits, XOR cancellation, bit reversal, bitmask pairing, maximum XOR with a bitwise trie.
  • 4. Math & Geometry (8 Paradigms, 14 Problems): Sieve of Eratosthenes, integer/roman conversions, modular arithmetic, geometric simulation.
  • 5. Tree/Graph Depth-First Search (15 Paradigms, 24 Problems): Path sums, lowest common ancestor, tree diameter, validating BSTs, tree DP (House Robber III, Binary Tree Cameras).
  • 6. Tree/Graph Breadth-First Search (5 Paradigms, 12 Problems): Level-order traversals, shortest path in unweighted graphs, zig-zag traversals, rotting oranges, word ladders.
  • 7. Graphs (12 Paradigms, 22 Problems): Topological sort, cycle detection, Dijkstra's algorithm, bipartite graph validation, network delay.
  • 8. Backtracking (6 Paradigms, 14 Problems): Subsets, permutations, combinations, constraint satisfaction, pruning, N-Queens, Sudoku solver.
  • 9. Dynamic Programming (13 Paradigms, 28 Problems): Memoization vs tabulation, 0/1 knapsack, unbounded knapsack, coin change, edit distance, longest common subsequence.
  • 10. Heap / Priority Queue (9 Paradigms, 11 Problems): Running medians, top-k elements, interval scheduling, IPO, k-way merges.
  • 11. Advanced Data Structures (7 Paradigms, 17 Problems): Trie (prefix tree), Union-Find (Disjoint Set Union), LRU Cache, LFU Cache, range-sum queries with updates.
  • 12. Stack (5 Paradigms, 9 Problems): Next greater element, largest rectangle in histogram, remove k digits, decode string, min stack.
  • 13. Intervals & Greedy (2 Paradigms, 6 Problems): Merge intervals, interval scheduling, meeting rooms by sweep line, jump game.

4-Stage Deliberate Practice Framework

  1. Stage 1 (Compare & Learn): Read the pattern's mental model, loop invariant and Big-O proof, compare the pattern's template with the problem's solution side by side, and use the step-by-step debugger. The solution can also be shown in C#, Java, TypeScript, C++, Go or Rust for almost every problem.
  2. Stage 2 (Active Recall): Rebuild the solution from memory in a code editor, with an invariant checklist and optional progressive hints, and check it against a judged test suite.
  3. Stage 3 (AI Interview Coach, coming soon): A mock-interview stage still in development; for now, students go straight from Stage 2 to Stage 4.
  4. Stage 4 (Solve on Your Own): Solve a new problem on your own against a 15-minute timer; your code is graded against a test suite. Python and TypeScript run in your browser and C# runs on our server (design problems grade in Python and TypeScript).

Equipped with a spaced-repetition review hub on a fixed 7-step schedule (1 day up to 90 days), a two-pane Studio workspace, and study notes.

Pricing, Access & Commercial Terms

  • Core Curriculum: 100% Free. No credit card required.
  • Coins: New accounts get 40 free coins once, the daily claim adds +20 coins once a day, and inviting a friend earns you +25 coins when they complete their first practice step. These are the only ways to earn coins.
  • AI Coach: Each AI coach question costs 1-5 coins, priced by the server according to its size.
  • BYOK (Bring Your Own Key): AI coaching is free with your own Google Gemini, OpenAI or OpenRouter API key (BYOK).
  • Extras: Coins also unlock extras for 24 hours: a Recall hint level (1 coin), a Solve solution reveal (2), a LeetCode problem card's solutions (1) and a pattern's extra canonical problems (1).
  • Payments & Refunds: Nothing on the site is sold for money: coins can only be earned, never bought. There are no subscriptions or charges, so there is nothing to refund.
  • Platform Operator: Hi👋WebEnterprise. Support & policies at hispeedalgo.com.
Skip to main content
Hi👋SpeedAlgo

Invariant-First Algorithmic Mastery

Arena
238Items
Theory Context•Stack
MediumLC 402

Remove K Digits (LeetCode 402)

Target Frequency:AmazonGoogleMicrosoft

You are given a non-negative whole number written as a string of decimal digits, num, and a count k. Delete exactly k of its digits, leaving the others where they are relative to each other, so that the number that is left is as small as possible.

Return that number as a string, written the usual way: without leading zeros, and as "0" when no digit is left or only zeros are.

Worked Examples

Example 1
Input:num = "1432219", k = 3
Output:"1219"
10413223241596removedremovedremoved
Explanation: Deleting the 4, the 3 and one of the 2s leaves 1219; no other choice of three digits leaves a smaller number.
Example 2
Input:num = "10200", k = 1
Output:"200"
1001220304removed
Explanation: Deleting the leading 1 leaves the digits 0200, which is the number 200: the answer is written without its leading zero.
Example 3
Input:num = "10", k = 2
Output:"0"
1001removedremoved
Explanation: Both digits are deleted. Nothing is left, and an empty number counts as 0.

⚖️Formal Constraints & Bounds

  • 1 <= k <= num.length <= 105

  • num consists of only digits.

  • num does not have any leading zeros except for the zero itself.

Deep-Dive & Conceptual Insights

Core Insight

The leftmost place that changes decides which number is smaller, so a bigger digit sitting right before a smaller one is always worth deleting: a stack pops such digits while removals remain, and any removals left over come off the end.

Real-World Scenario & Production Applications

Identifiers, version strings and sort keys are compared the way numbers are, from the left: the first place that differs decides. Whenever a system must shorten such a sequence by a fixed number of characters and keep the rest in order (trimming a key to a length budget, sampling a sequence down), the best result spends every deletion on the earliest place it can lower, which is exactly what the pop-bigger stack does in one pass.

Step-by-Step Execution Trace Table

Example 2, num = "10200", k = 1 (the trap case: leading zeros):

digitwhile k > 0 and stack and stack[-1] > digitWhat the code doesstack afterk after
1stack is empty: nopush 1["1"]1
0"1" > "0": yes, then k is 0: nopop 1, k -= 1, push 0["0"]0
2k is 0: nopush 2["0", "2"]0
0k is 0: nopush 0["0", "2", "0"]0
0k is 0: nopush 0["0", "2", "0", "0"]0
after the loopstack[:4 - 0] keeps every digit["0", "2", "0", "0"]0
"0200".lstrip("0") gives "200": without it the answer would be "0200"

return result or "0" gives "200".

Code Shape
Archetype Code Shape
Python
1Numbers of the same length are compared from the left, so the earliest digit you can lower matters most: a bigger digit right before a smaller one is always worth deleting.
2Keep a `stack` of the digits you keep. While `k > 0` it never decreases from bottom to top: before pushing `digit`, pop every `stack[-1] > digit` and spend one removal for each.
3Loop `for digit in num:` with `while k > 0 and stack and stack[-1] > digit:` popping and doing `k -= 1`, then `stack.append(digit)`. After the loop, `stack = stack[:len(stack) - k]` spends the removals left over on the tail.
4The trap: the number is written without leading zeros. Return `"".join(stack).lstrip("0") or "0"`: `"10200"` with `k = 1` leaves `"0200"`, which must be `"200"`, and an empty result is `"0"`.

Conceptual Narrative

🧭 Conceptual Foundation & Pattern Intuition

Two numbers with the same number of digits are compared from the left: the first place where they differ decides. So when you must delete k digits, the digit to delete first is the one whose removal lowers the earliest place. The Lexicographic Stack finds those digits in one pass. It keeps the digits it wants on a stack; when a new digit is smaller than the digit on top and removals remain, the top digit goes, because the smaller digit then moves one place to the left. It keeps popping while that holds, and then pushes digit.

🧗 The Analogy: A Line of Climbers Sorted by Height

Climbers arrive one by one to form a rope team, and you may send k of them home, but never reorder them. You want the team to look as short as possible from the front. When a shorter climber arrives while a taller one stands just ahead, send the taller one home, and keep doing it while you still can: the shorter climber moves forward. When you run out of arrivals with home tickets left, the team already rises from front to back, so the tallest climbers are the last ones: send them home from the back.

🔑 Why It Works

A pop never has to be undone: once a smaller digit takes an earlier place, no set of later deletions can put a smaller digit there. The last line is the trap: the answer is a number, so it is written without leading zeros, and an empty result is "0".

  • Leading zeros and an empty result (the trap): "".join(stack) can start with zeros or be empty: popping the 1 of "10200" leaves "0200". Strip them with .lstrip("0") and return result or "0", so the answers are "200" and, for "10" with k = 2, "0".

  • Removals left over: when num already rises, as in "12345" with k = 2, the loop pops nothing. Cut the tail with stack[:len(stack) - k]; stack[:-k] empties the stack when k is 0.

  • if instead of while: one small digit may have to clear several bigger ones: "9991" with k = 3 must pop all three 9s and return "1", not "9".

  • Popping equal digits: use stack[-1] > digit, not >=: "1121" with k = 1 must return "111"; popping the equal 1 wastes the removal and returns "121".

  • Deleting digits one at a time: rescanning from the left for the first digit bigger than the one after it is correct but O(N · k), about 5 * 109 steps at these limits; the stack does all k removals in one pass.

Senior SWE Reasoning Architecture

4-Phase Thought Process Model

Pattern Recognition Signals

The 10-second spot

"Delete exactly k of its digits" to get a number "as small as possible", with the remaining digits kept in their order: a budget of deletions and a result that is compared from the left. The earliest place that changes decides the comparison, so each new digit should knock out the bigger digits in front of it while the budget lasts: the Lexicographic Stack.

Formulating the Predicate & Invariants

Turning intuition into a boolean rule

Decision rule for each digit: while k > 0 and stack and stack[-1] > digit, pop and spend one removal; then push digit.

Silent Failure Traps & Edge Cases

Where confident candidates still lose points

  • Keep stack a list, not a string: popping with s = s[:-1] copies the whole string every time, which turns the pass quadratic on 105 digits.

The 60-Second Interview Pitch

Say this out loud before you type a single line

I'd use a Lexicographic Stack. Two numbers of the same length are compared from the left, so the earliest digit I can lower matters most. I walk the digits once and keep a stack of the digits I'm keeping. When the new digit is smaller than the top of the stack and I still have removals, I pop the top and spend one removal, and I keep popping while that holds; then I push the new digit. A bigger digit right before a smaller one is always the best deletion, so no pop ever has to be undone. If removals are left at the end, the stack never decreases, so I cut them off the tail. The trap is the output: I strip the leading zeros, and return zero if nothing is left. Each digit is pushed once and popped at most once, so it's O(N) time and O(N) space.

Big-O Invariant Derivation

Complexity & Mathematical Proof

TIME COMPLEXITY

O(N)

Look at the code: for digit in num runs N times, and each digit is pushed once by stack.append(digit). The inner while can pop several digits at one step, but every pop removes a digit that was pushed earlier, and a digit is popped at most once, so all the pops of the whole run add up to at most N (in fact at most k). The slice stack[:len(stack) - k], the join and lstrip("0") each walk at most N characters once. Total: O(N).

SPACE COMPLEXITY

O(N)

stack holds at most N digits (all of them when nothing is popped), and the slice and the joined result are at most N characters each. Space: O(N), the returned string included.

Formal Recurrence Relation

T(N) = N pushes + at most N pops + O(N) to cut, join and strip = O(N)

Derivation Progression

Digit loop

N iterations

for digit in num reads each digit once and pushes it once with stack.append(digit).

Pops (the inner while)

at most N in total

Each pop removes a digit pushed earlier, and a digit is popped at most once, so the while runs at most N extra times over the whole run (and at most k times).

Cut, join, strip

O(N)

stack[:len(stack) - k], "".join(stack) and .lstrip("0") each walk at most N characters.

Total

O(N)

N pushes, at most N pops and three linear passes.

Variable Definitions

NNN

Number of digits, len(num) (at most 105)

kkk

Digits still to delete (at most N)

Memory Architecture & Bounds

🟣 Call Stack

O(1) Iterative, no recursion

🔵 Auxiliary Heap

O(N): stack holds at most N digits

🟢 Output Space

O(N): the returned string

Boundary Best / Worst Cases

Best Case

O(N)O(N)O(N): every digit is still read and pushed once, even when nothing is popped

Average Case

O(N)O(N)O(N)

Worst Case

O(N)O(N)O(N): at most N pops in total, however they are spread over the digits

Recurrence Tree Topology

Recurrence Tree Topology
Synthesizing vector architecture diagram...
Staff+ Engineering Perspective

Senior SWE Deconstruction & Hardware Caveats

ARCHITECTURAL SIGNALS & INTERVIEW TRIGGERS

Triggers: "Delete exactly k of its digits", "as small as possible". A fixed budget of deletions, the rest kept in order, and a result compared from the left: the Lexicographic Stack, which pops a bigger item whenever a smaller one arrives and the budget allows.

CONSTRAINTS & BOUNDS

N≤105N \le 10^5N≤105 digits and k≤Nk \le Nk≤N. Deleting one digit at a time with a rescan from the left is O(N⋅k)O(N \cdot k)O(N⋅k), about 5⋅1095 \cdot 10^95⋅109 steps at k=N/2k = N / 2k=N/2; the stack pushes each digit once and pops it at most once: O(N)O(N)O(N) time, O(N)O(N)O(N) memory.

FAANG PRODUCTION TRAPS & EDGE CASES

On a stream, a digit that is pushed may still be popped by a later, smaller digit while the budget lasts, so nothing can be emitted before the budget is spent or the input ends: the stack is the buffer.

Core Algorithmic State Invariants

  1. The Budget Is the Only Brake

Without k > 0 the loop would pop until stack never decreases and could delete more digits than allowed. The budget is what stops it: once k reaches 0, every remaining digit is kept as it comes.

  1. Largest Instead of Smallest

Flip the comparison to stack[-1] < digit and the same pass keeps the largest possible number instead: a smaller digit in front of a bigger one is now the one to delete.

Theory Context•Stack
MediumLC 402

Remove K Digits (LeetCode 402)

Target Frequency:AmazonGoogleMicrosoft

You are given a non-negative whole number written as a string of decimal digits, num, and a count k. Delete exactly k of its digits, leaving the others where they are relative to each other, so that the number that is left is as small as possible.

Return that number as a string, written the usual way: without leading zeros, and as "0" when no digit is left or only zeros are.

Worked Examples

Example 1
Input:num = "1432219", k = 3
Output:"1219"
10413223241596removedremovedremoved
Explanation: Deleting the 4, the 3 and one of the 2s leaves 1219; no other choice of three digits leaves a smaller number.
Example 2
Input:num = "10200", k = 1
Output:"200"
1001220304removed
Explanation: Deleting the leading 1 leaves the digits 0200, which is the number 200: the answer is written without its leading zero.
Example 3
Input:num = "10", k = 2
Output:"0"
1001removedremoved
Explanation: Both digits are deleted. Nothing is left, and an empty number counts as 0.

⚖️Formal Constraints & Bounds

  • 1 <= k <= num.length <= 105

  • num consists of only digits.

  • num does not have any leading zeros except for the zero itself.

Deep-Dive & Conceptual Insights

Core Insight

The leftmost place that changes decides which number is smaller, so a bigger digit sitting right before a smaller one is always worth deleting: a stack pops such digits while removals remain, and any removals left over come off the end.

Real-World Scenario & Production Applications

Identifiers, version strings and sort keys are compared the way numbers are, from the left: the first place that differs decides. Whenever a system must shorten such a sequence by a fixed number of characters and keep the rest in order (trimming a key to a length budget, sampling a sequence down), the best result spends every deletion on the earliest place it can lower, which is exactly what the pop-bigger stack does in one pass.

Step-by-Step Execution Trace Table

Example 2, num = "10200", k = 1 (the trap case: leading zeros):

digitwhile k > 0 and stack and stack[-1] > digitWhat the code doesstack afterk after
1stack is empty: nopush 1["1"]1
0"1" > "0": yes, then k is 0: nopop 1, k -= 1, push 0["0"]0
2k is 0: nopush 2["0", "2"]0
0k is 0: nopush 0["0", "2", "0"]0
0k is 0: nopush 0["0", "2", "0", "0"]0
after the loopstack[:4 - 0] keeps every digit["0", "2", "0", "0"]0
"0200".lstrip("0") gives "200": without it the answer would be "0200"

return result or "0" gives "200".

Code Shape
Archetype Code Shape
Python
1Numbers of the same length are compared from the left, so the earliest digit you can lower matters most: a bigger digit right before a smaller one is always worth deleting.
2Keep a `stack` of the digits you keep. While `k > 0` it never decreases from bottom to top: before pushing `digit`, pop every `stack[-1] > digit` and spend one removal for each.
3Loop `for digit in num:` with `while k > 0 and stack and stack[-1] > digit:` popping and doing `k -= 1`, then `stack.append(digit)`. After the loop, `stack = stack[:len(stack) - k]` spends the removals left over on the tail.
4The trap: the number is written without leading zeros. Return `"".join(stack).lstrip("0") or "0"`: `"10200"` with `k = 1` leaves `"0200"`, which must be `"200"`, and an empty result is `"0"`.

Conceptual Narrative

🧭 Conceptual Foundation & Pattern Intuition

Two numbers with the same number of digits are compared from the left: the first place where they differ decides. So when you must delete k digits, the digit to delete first is the one whose removal lowers the earliest place. The Lexicographic Stack finds those digits in one pass. It keeps the digits it wants on a stack; when a new digit is smaller than the digit on top and removals remain, the top digit goes, because the smaller digit then moves one place to the left. It keeps popping while that holds, and then pushes digit.

🧗 The Analogy: A Line of Climbers Sorted by Height

Climbers arrive one by one to form a rope team, and you may send k of them home, but never reorder them. You want the team to look as short as possible from the front. When a shorter climber arrives while a taller one stands just ahead, send the taller one home, and keep doing it while you still can: the shorter climber moves forward. When you run out of arrivals with home tickets left, the team already rises from front to back, so the tallest climbers are the last ones: send them home from the back.

🔑 Why It Works

A pop never has to be undone: once a smaller digit takes an earlier place, no set of later deletions can put a smaller digit there. The last line is the trap: the answer is a number, so it is written without leading zeros, and an empty result is "0".

  • Leading zeros and an empty result (the trap): "".join(stack) can start with zeros or be empty: popping the 1 of "10200" leaves "0200". Strip them with .lstrip("0") and return result or "0", so the answers are "200" and, for "10" with k = 2, "0".

  • Removals left over: when num already rises, as in "12345" with k = 2, the loop pops nothing. Cut the tail with stack[:len(stack) - k]; stack[:-k] empties the stack when k is 0.

  • if instead of while: one small digit may have to clear several bigger ones: "9991" with k = 3 must pop all three 9s and return "1", not "9".

  • Popping equal digits: use stack[-1] > digit, not >=: "1121" with k = 1 must return "111"; popping the equal 1 wastes the removal and returns "121".

  • Deleting digits one at a time: rescanning from the left for the first digit bigger than the one after it is correct but O(N · k), about 5 * 109 steps at these limits; the stack does all k removals in one pass.

Senior SWE Reasoning Architecture

4-Phase Thought Process Model

Pattern Recognition Signals

The 10-second spot

"Delete exactly k of its digits" to get a number "as small as possible", with the remaining digits kept in their order: a budget of deletions and a result that is compared from the left. The earliest place that changes decides the comparison, so each new digit should knock out the bigger digits in front of it while the budget lasts: the Lexicographic Stack.

Formulating the Predicate & Invariants

Turning intuition into a boolean rule

Decision rule for each digit: while k > 0 and stack and stack[-1] > digit, pop and spend one removal; then push digit.

Silent Failure Traps & Edge Cases

Where confident candidates still lose points

  • Keep stack a list, not a string: popping with s = s[:-1] copies the whole string every time, which turns the pass quadratic on 105 digits.

The 60-Second Interview Pitch

Say this out loud before you type a single line

I'd use a Lexicographic Stack. Two numbers of the same length are compared from the left, so the earliest digit I can lower matters most. I walk the digits once and keep a stack of the digits I'm keeping. When the new digit is smaller than the top of the stack and I still have removals, I pop the top and spend one removal, and I keep popping while that holds; then I push the new digit. A bigger digit right before a smaller one is always the best deletion, so no pop ever has to be undone. If removals are left at the end, the stack never decreases, so I cut them off the tail. The trap is the output: I strip the leading zeros, and return zero if nothing is left. Each digit is pushed once and popped at most once, so it's O(N) time and O(N) space.

Big-O Invariant Derivation

Complexity & Mathematical Proof

TIME COMPLEXITY

O(N)

Look at the code: for digit in num runs N times, and each digit is pushed once by stack.append(digit). The inner while can pop several digits at one step, but every pop removes a digit that was pushed earlier, and a digit is popped at most once, so all the pops of the whole run add up to at most N (in fact at most k). The slice stack[:len(stack) - k], the join and lstrip("0") each walk at most N characters once. Total: O(N).

SPACE COMPLEXITY

O(N)

stack holds at most N digits (all of them when nothing is popped), and the slice and the joined result are at most N characters each. Space: O(N), the returned string included.

Formal Recurrence Relation

T(N) = N pushes + at most N pops + O(N) to cut, join and strip = O(N)

Derivation Progression

Digit loop

N iterations

for digit in num reads each digit once and pushes it once with stack.append(digit).

Pops (the inner while)

at most N in total

Each pop removes a digit pushed earlier, and a digit is popped at most once, so the while runs at most N extra times over the whole run (and at most k times).

Cut, join, strip

O(N)

stack[:len(stack) - k], "".join(stack) and .lstrip("0") each walk at most N characters.

Total

O(N)

N pushes, at most N pops and three linear passes.

Variable Definitions

NNN

Number of digits, len(num) (at most 105)

kkk

Digits still to delete (at most N)

Memory Architecture & Bounds

🟣 Call Stack

O(1) Iterative, no recursion

🔵 Auxiliary Heap

O(N): stack holds at most N digits

🟢 Output Space

O(N): the returned string

Boundary Best / Worst Cases

Best Case

O(N)O(N)O(N): every digit is still read and pushed once, even when nothing is popped

Average Case

O(N)O(N)O(N)

Worst Case

O(N)O(N)O(N): at most N pops in total, however they are spread over the digits

Recurrence Tree Topology

Recurrence Tree Topology
Synthesizing vector architecture diagram...
Staff+ Engineering Perspective

Senior SWE Deconstruction & Hardware Caveats

ARCHITECTURAL SIGNALS & INTERVIEW TRIGGERS

Triggers: "Delete exactly k of its digits", "as small as possible". A fixed budget of deletions, the rest kept in order, and a result compared from the left: the Lexicographic Stack, which pops a bigger item whenever a smaller one arrives and the budget allows.

CONSTRAINTS & BOUNDS

N≤105N \le 10^5N≤105 digits and k≤Nk \le Nk≤N. Deleting one digit at a time with a rescan from the left is O(N⋅k)O(N \cdot k)O(N⋅k), about 5⋅1095 \cdot 10^95⋅109 steps at k=N/2k = N / 2k=N/2; the stack pushes each digit once and pops it at most once: O(N)O(N)O(N) time, O(N)O(N)O(N) memory.

FAANG PRODUCTION TRAPS & EDGE CASES

On a stream, a digit that is pushed may still be popped by a later, smaller digit while the budget lasts, so nothing can be emitted before the budget is spent or the input ends: the stack is the buffer.

Core Algorithmic State Invariants

  1. The Budget Is the Only Brake

Without k > 0 the loop would pop until stack never decreases and could delete more digits than allowed. The budget is what stops it: once k reaches 0, every remaining digit is kept as it comes.

  1. Largest Instead of Smallest

Flip the comparison to stack[-1] < digit and the same pass keeps the largest possible number instead: a smaller digit in front of a bigger one is now the one to delete.

© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•