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•Two Pointers & Sliding Window

Builds on Hash Map, which you haven’t finished yet.

P1MediumMental model~3 min speedrun

Bucket Sort (Top K Frequent, Maximum Gap)

Grows into
Counting Sort

Maximum Gap (LC 164)(opens leetcode.com)

You get an integer array nums. Picture its values sorted from smallest to largest: each pair of values that end up side by side has a difference, and you must return the largest of those differences. If nums holds fewer than two values, there is no such pair, so return 0.

Sorting only defines the answer; it is not a way you may use to find it. Your algorithm has to run in time linear in the length of nums, and it may use extra space that is also linear in it.

Example 1
Input:nums = [3,6,9,1]
Output:3
Sorted, the values read 1, 3, 6, 9. The differences between neighbours are 2, 3 and 3, so the largest is 3; both 3 → 6 and 6 → 9 reach it.
306192136 - 3 = 39 - 6 = 3

Bucket Sort

Spot it when: Values or counts fall in a known, bounded range (every count is between 1 and n; n values spread over min to max), and you need their order, the top k, or the largest gap in linear time, without an O(n log n) sort.
Not this one if…
  • Values arrive one at a time and the answer is needed after every arrival, as in Kth Largest Element in a Stream (LC 703): there is no fixed range to lay the buckets over before the data starts. See Top-K Heap
  • Every value that matters is a whole number no larger than the input length, as in H-Index (LC 274). There, any citation count above n can be treated as n. One slot for each exact value is enough. No bucket needs to cover a range, and nothing inside a slot needs to be compared. See Counting Sort
Bucket Sort
Python
1def maximum_gap(nums: list[int]) -> int:
2 """Maximum Gap: pigeonhole buckets, compare neighbours across buckets only.
3 Time: O(N) | Space: O(N)
4 """
5 # 1. Handle Too Few Values:
6 # With fewer than two values there is no neighbour at all, so no gap can be measured.
7 n = len(nums)
8 if n < 2:
9 return 0
10 
11 # 2. Size The Buckets From The Pigeonhole Bound:
12 # The largest gap is at least the average gap (hi - lo) / (n - 1); max(1, ...) keeps every
13 # bucket at least one value wide so the later division never hits zero.
14 lo, hi = min(nums), max(nums)
15 width = max(1, (hi - lo) // (n - 1))
16 count = (hi - lo) // width + 1 # Enough buckets to hold hi as well
17 bucket_min = [float("inf")] * count # Smallest value that fell in each bucket
18 bucket_max = [float("-inf")] * count # Largest value that fell in each bucket
19 
20 # 3. Drop Each Value Into Its Bucket, Keeping Only The Extremes:
21 # No two values are compared here; because width is at most the smallest possible gap, the
22 # answer can never sit inside one bucket, so only its smallest and largest value matter.
23 for x in nums:
24 b = (x - lo) // width
25 bucket_min[b] = min(bucket_min[b], x)
26 bucket_max[b] = max(bucket_max[b], x)
27 
28 # 4. Walk The Buckets In Order And Measure Cross-Bucket Gaps:
29 # Skipping empty buckets and carrying prev_max forward means each gap is measured from the
30 # last non-empty bucket's maximum to the next non-empty bucket's minimum.
31 best = 0
32 prev_max = lo
33 for b in range(count):
34 if bucket_min[b] == float("inf"):
35 continue
36 best = max(best, bucket_min[b] - prev_max)
37 prev_max = bucket_max[b]
38 
39 return best
Invariant: Bucket b holds exactly the values in [lo + b * width, lo + (b + 1) * width), and width is at most the smallest possible maximum gap.

Bucket Sort drops each value straight into the bucket that covers its range, b = (x - lo) // width, so placing a value needs no comparison with other values and the buckets come out in order. The key can be any bounded number: bucket by value range to find gaps, or by count to order items by frequency. For the largest gap between neighbours, keep only bucket_min[b] and bucket_max[b], walk the buckets once, and measure bucket_min[b] - prev_max, where prev_max is the largest value of the last non-empty bucket. That is enough because of the pigeonhole principle: n values from lo to hi leave n - 1 gaps that add up to hi - lo, so the largest gap is at least their average, and being a whole number it is at least width whenever hi > lo. Two values in the same bucket differ by at most width - 1, so the maximum gap is never inside one bucket: it always runs from one bucket's maximum to a later bucket's minimum, and the values in between never matter. The trap is the floor of 1: when hi - lo < n - 1, for example with repeated values, (hi - lo) // (n - 1) is 0 and (x - lo) // width divides by zero, so write width = max(1, (hi - lo) // (n - 1)).

See it run

Bucket Sort, step by step

x - · b - · prev_max - · best 0

x 1 · b 0 · prev_max - · best 0

x 2 · b 0 · prev_max - · best 0

x 10 · b 2 · prev_max - · best 0

x 4 · b 0 · prev_max - · best 0

x 25 · b 6 · prev_max - · best 0

x 7 · b 1 · prev_max - · best 0

x - · b 0 · prev_max 1 · best 0

x - · b 0 · prev_max 4 · best 0

x - · b 1 · prev_max 4 · best 3

x - · b 1 · prev_max 7 · best 3

x - · b 2 · prev_max 7 · best 3

x - · b 2 · prev_max 10 · best 3

x - · b 3 · prev_max 10 · best 3

x - · b 4 · prev_max 10 · best 3

x - · b 5 · prev_max 10 · best 3

x - · b 6 · prev_max 10 · best 15

x - · b 6 · prev_max 10 · best 15

x - · b 6 · prev_max 25 · best 15

x - · b 6 · prev_max 25 · best 15 · answer 15

Each column is one bucket. The top row is bucket_min, the middle row is bucket_max, and the bottom row is the gap measured when the walk reaches that bucket.

  • put the bucket that receives the value x
  • gap the bucket whose minimum is measured against prev_max
  • prev_max the bucket whose maximum becomes prev_max
  • empty a bucket with no values, skipped in the walk

Start: lo = 1, hi = 25, n = 6, so width = max(1, (25 - 1) // 5) = 4. That makes 7 buckets.

x = 1, so b = (1 - 1) // 4 = 0. bucket_min[0] = 1 and bucket_max[0] = 1.

x = 2, so b = (2 - 1) // 4 = 0. bucket_min[0] = 1 and bucket_max[0] = 2.

x = 10, so b = (10 - 1) // 4 = 2. bucket_min[2] = 10 and bucket_max[2] = 10.

x = 4, so b = (4 - 1) // 4 = 0. bucket_min[0] = 1 and bucket_max[0] = 4.

x = 25, so b = (25 - 1) // 4 = 6. bucket_min[6] = 25 and bucket_max[6] = 25.

x = 7, so b = (7 - 1) // 4 = 1. bucket_min[1] = 7 and bucket_max[1] = 7.

gap = bucket_min[0] - prev_max = 1 - 1 = 0. best is now 0.

prev_max = bucket_max[0] = 4.

gap = bucket_min[1] - prev_max = 7 - 4 = 3. best is now 3.

prev_max = bucket_max[1] = 7.

gap = bucket_min[2] - prev_max = 10 - 7 = 3. best is now 3.

prev_max = bucket_max[2] = 10.

Bucket 3 is empty, so it is skipped. prev_max stays 10.

Bucket 4 is empty, so it is skipped. prev_max stays 10.

Bucket 5 is empty, so it is skipped. prev_max stays 10.

gap = bucket_min[6] - prev_max = 25 - 10 = 15. best is now 15.

Trap: bucket 5 is empty, so bucket_max[5] is -inf and the gap would be inf. prev_max = 10 comes from bucket 2, the last bucket that was not empty.

prev_max = bucket_max[6] = 25.

Done: the largest gap between neighbours is 15.

bucket_min
b0inf
b1inf
b2inf
b3inf
b4inf
b5inf
b6inf
bucket_max
-inf
-inf
-inf
-inf
-inf
-inf
-inf
gap
1 / 20
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Computing width without the floor of 1

    Mistake
    width = (hi - lo) // (n - 1)
    Fix
    width = max(1, (hi - lo) // (n - 1))

    What happens: With repeated values, hi - lo can be smaller than n - 1. Then (hi - lo) // (n - 1) is 0. The next division, (hi - lo) // width, divides by zero. max(1, ...) keeps every bucket at least one value wide.

    Example: nums = [1, 1, 1, 4, 2]
    Mistake: raises ZeroDivisionErrorFix: returns 2
  2. 2

    Gotcha 2: Leaving out the check if n < 2

    Mistake
    n = len(nums)
    Fix
    n = len(nums)
    if n < 2:
        return 0

    What happens: With one value, there is no neighbour and no gap, so the answer is 0. Without the check, the code goes on to compute the width. n - 1 is 0 there, so (hi - lo) // (n - 1) divides by zero.

    Example: nums = [10]
    Mistake: raises ZeroDivisionErrorFix: returns 0
  3. 3

    Gotcha 3: Rounding the width up with + 1

    Mistake
    width = (hi - lo) // (n - 1) + 1
    Fix
    width = max(1, (hi - lo) // (n - 1))

    What happens: The largest gap is at least (hi - lo) / (n - 1), the average gap. A bucket must be no wider than that, so the largest gap can never be inside one bucket. With + 1, a bucket can be wider. Two values can then share a bucket, and the gap between them is never measured.

    Example: nums = [6, 18]
    Mistake: returns 0Fix: returns 12
  4. 4

    Gotcha 4: Comparing with bucket_max[b - 1] instead of prev_max

    Mistake
    best = max(best, bucket_min[b] - bucket_max[b - 1])
    Fix
    best = max(best, bucket_min[b] - prev_max)
    prev_max = bucket_max[b]

    What happens: The largest gap often runs across empty buckets. The value just below bucket_min[b] is then in an earlier bucket, not in bucket b - 1. An empty bucket_max[b - 1] is -inf, and the difference becomes inf. prev_max keeps the maximum of the last bucket that was not empty.

    Example: nums = [1, 2, 10]
    Mistake: returns infFix: returns 8

Deep Dive

The procedure: compute the bucket from the value

A bucket is a place that holds every value inside one range. Bucket Sort puts each value into its bucket with one formula, b = (x - lo) // width. The formula uses only x, lo and width. It never compares x with another value.

The buckets are laid out in order. Bucket 0 holds the smallest values and the last bucket holds the largest. So after the placing loop, the values are already ordered between buckets. Only the order inside one bucket is unknown.

The key can be any bounded number. In maximum_gap the key is the value itself, cut into ranges of width numbers. In Top K Frequent Elements (LC 347) and Sort Characters By Frequency (LC 451) the key is a count. A count is never more than the input length, so buckets[count] can hold every item with that count.

For example, take nums = [1, 1, 1, 2, 2, 3] and k = 2. The counts are 3 for the value 1, 2 for the value 2 and 1 for the value 3. buckets[3] holds [1], buckets[2] holds [2] and buckets[1] holds [3]. Reading buckets from the last index down gives 1, then 2, so the answer is [1, 2].

Maximum Gap, traced on six numbers

Take nums = [1, 2, 10, 4, 25, 7]. Here n = 6, lo = 1 and hi = 25. So width = max(1, 24 // 5) = 4, and count = 24 // 4 + 1 = 7 buckets.

The placing loop gives these buckets. Bucket 0 holds 1, 2 and 4, so its minimum is 1 and its maximum is 4. Bucket 1 holds 7. Bucket 2 holds 10. Buckets 3, 4 and 5 stay empty. Bucket 6 holds 25.

The walk starts with prev_max = lo = 1. Bucket 0 gives a gap of 1 - 1 = 0. Bucket 1 gives 7 - 4 = 3, and bucket 2 gives 10 - 7 = 3. The board marks buckets 3, 4 and 5 as skipped.

Bucket 6 gives 25 - 10 = 15. The last non-empty bucket before it is bucket 2, not bucket 5. This is the trap on the board: bucket_max[5] is -inf, so the formula with bucket_max[b - 1] would give inf. The variable prev_max is 10 and gives the right gap.

The answer is 15. On the LeetCode example [3, 6, 9, 1] the same code returns 3.

Why the answer is never inside one bucket

There are n values, so there are n - 1 gaps between sorted neighbours. The gaps add up to hi - lo. The largest gap is at least the average, (hi - lo) / (n - 1). A gap is a whole number, so the largest gap is at least (hi - lo) // (n - 1), which is width, whenever hi > lo. This is the pigeonhole principle.

A bucket covers width whole numbers, so two values in one bucket differ by at most width - 1. That is smaller than width. So no pair inside one bucket can be the largest gap. The largest gap always starts at the maximum of one non-empty bucket and ends at the minimum of a later non-empty bucket. The other values in a bucket never matter, so each bucket keeps only bucket_min and bucket_max.

The first trap is width = (hi - lo) // (n - 1) with no floor of 1. For nums = [1, 1, 1, 4, 2], hi - lo = 3 and n - 1 = 4, so the result is 0 and the next division raises a ZeroDivisionError. With max(1, ...), width = 1 and the answer is 2. The second trap is a width rounded up with + 1. For nums = [6, 18] the width becomes 13, both values share bucket 0, and the code returns 0 instead of 12.

Cost, and when not to use buckets

The placing loop does O(n) work and the walk visits every bucket once. The number of buckets is (hi - lo) // width + 1, which is less than 2n. So the time is O(n) and the space is O(n). Sorting first costs O(n log n).

Buckets need the range to be known before the data is placed. If the values arrive one at a time and the answer is needed after each arrival, as in Kth Largest Element in a Stream (LC 703), there is no range to lay out. Use a heap there.

If every value is a small whole number, one slot per exact value is enough and no range is needed. That is the next card, Counting Sort.

60-Second Pitch

"Sorting costs O(n log n), so I use buckets. There are n values and n - 1 gaps that add up to hi - lo, so the largest gap is at least (hi - lo) // (n - 1). I make each bucket that wide, with a floor of 1. Each value goes to the bucket (x - lo) // width, and each bucket keeps only its minimum and maximum. Two values in one bucket are closer than the width, so the answer is never inside a bucket. I walk the buckets in order, skip the empty ones, and measure each non-empty bucket's minimum against prev_max. The time is O(n) and the space is O(n). The two traps are the floor of 1 on width, and using prev_max instead of the previous bucket's maximum."

Common Questions

Why is it enough to keep only the minimum and maximum of each bucket?

Two values in one bucket differ by at most width - 1, and the largest gap is at least width. So the largest gap is never between two values of one bucket. It always runs from the maximum of one bucket to the minimum of a later bucket.

What is the pigeonhole argument here?

There are n - 1 gaps between sorted neighbours, and they add up to hi - lo. The largest of them is at least the average. If every bucket is narrower than the average gap, two neighbours cannot be in the same bucket and span the largest gap.

What happens when all values are equal?

Then hi - lo is 0, so (hi - lo) // (n - 1) is 0. The floor gives width = 1 and count = 1. All values fall in bucket 0, and the walk measures a gap of 0.

How does Top K Frequent Elements (LC 347) use buckets?

Count the values with a dictionary. A count is at most len(nums), so make a list buckets with len(nums) + 1 entries and put each value in buckets[count]. Read the list from the last index down until k values are collected. The time is O(n).

When would you use a heap or a sort instead?

Use a heap when values arrive one at a time and the range is not known, as in Kth Largest Element in a Stream (LC 703). Use a sort when n is small and the code must be short. Buckets win when the range is known and the limit needs O(n).

SWE Reasoning

Spot it

  • The values lie in a known, bounded range, and a sort at O(n log n) is too slow for the limit.
  • The question asks for the largest gap between neighbours in sorted order, as in Maximum Gap (LC 164), but the array may not be sorted.
  • The key is a count that is never more than n, and the items must come out ordered by it, as in Sort Characters By Frequency (LC 451) or Top K Frequent Elements (LC 347).

The invariant, step by step

  1. lo and hi are the smallest and largest value. width = max(1, (hi - lo) // (n - 1)) is a number that is at most the smallest possible largest gap.
  2. Bucket b holds exactly the values in [lo + b * width, lo + (b + 1) * width).
  3. Each value goes to bucket b = (x - lo) // width. The bucket keeps only its smallest and largest value.
  4. The walk visits the buckets in order. It skips empty buckets and keeps prev_max, the largest value of the last non-empty bucket.
  5. The gap bucket_min[b] - prev_max is measured only across buckets, and the largest of these is the answer.

Traps

  • No floor of 1 on width. With repeated values, (hi - lo) // (n - 1) is 0 and the next division raises a ZeroDivisionError.
  • No check for n < 2. With one value, n - 1 is 0 and the division raises a ZeroDivisionError. The answer is 0.
  • A width rounded up with + 1. Two values can then share a bucket, and the gap between them is never measured. [6, 18] returns 0 instead of 12.
  • Using bucket_max[b - 1] instead of prev_max. An empty previous bucket has the maximum -inf, and the gap becomes inf.

Practice

Canonical LeetCode:Maximum Gap (LC 164)Sort Characters By Frequency (LC 451)Top K Frequent Elements (LC 347)
Theory Context•Two Pointers & Sliding Window

Builds on Hash Map, which you haven’t finished yet.

P1MediumMental model~3 min speedrun

Bucket Sort (Top K Frequent, Maximum Gap)

Grows into
Counting Sort

Maximum Gap (LC 164)(opens leetcode.com)

You get an integer array nums. Picture its values sorted from smallest to largest: each pair of values that end up side by side has a difference, and you must return the largest of those differences. If nums holds fewer than two values, there is no such pair, so return 0.

Sorting only defines the answer; it is not a way you may use to find it. Your algorithm has to run in time linear in the length of nums, and it may use extra space that is also linear in it.

Example 1
Input:nums = [3,6,9,1]
Output:3
Sorted, the values read 1, 3, 6, 9. The differences between neighbours are 2, 3 and 3, so the largest is 3; both 3 → 6 and 6 → 9 reach it.
306192136 - 3 = 39 - 6 = 3

Bucket Sort

Spot it when: Values or counts fall in a known, bounded range (every count is between 1 and n; n values spread over min to max), and you need their order, the top k, or the largest gap in linear time, without an O(n log n) sort.
Not this one if…
  • Values arrive one at a time and the answer is needed after every arrival, as in Kth Largest Element in a Stream (LC 703): there is no fixed range to lay the buckets over before the data starts. See Top-K Heap
  • Every value that matters is a whole number no larger than the input length, as in H-Index (LC 274). There, any citation count above n can be treated as n. One slot for each exact value is enough. No bucket needs to cover a range, and nothing inside a slot needs to be compared. See Counting Sort
Bucket Sort
Python
1def maximum_gap(nums: list[int]) -> int:
2 """Maximum Gap: pigeonhole buckets, compare neighbours across buckets only.
3 Time: O(N) | Space: O(N)
4 """
5 # 1. Handle Too Few Values:
6 # With fewer than two values there is no neighbour at all, so no gap can be measured.
7 n = len(nums)
8 if n < 2:
9 return 0
10 
11 # 2. Size The Buckets From The Pigeonhole Bound:
12 # The largest gap is at least the average gap (hi - lo) / (n - 1); max(1, ...) keeps every
13 # bucket at least one value wide so the later division never hits zero.
14 lo, hi = min(nums), max(nums)
15 width = max(1, (hi - lo) // (n - 1))
16 count = (hi - lo) // width + 1 # Enough buckets to hold hi as well
17 bucket_min = [float("inf")] * count # Smallest value that fell in each bucket
18 bucket_max = [float("-inf")] * count # Largest value that fell in each bucket
19 
20 # 3. Drop Each Value Into Its Bucket, Keeping Only The Extremes:
21 # No two values are compared here; because width is at most the smallest possible gap, the
22 # answer can never sit inside one bucket, so only its smallest and largest value matter.
23 for x in nums:
24 b = (x - lo) // width
25 bucket_min[b] = min(bucket_min[b], x)
26 bucket_max[b] = max(bucket_max[b], x)
27 
28 # 4. Walk The Buckets In Order And Measure Cross-Bucket Gaps:
29 # Skipping empty buckets and carrying prev_max forward means each gap is measured from the
30 # last non-empty bucket's maximum to the next non-empty bucket's minimum.
31 best = 0
32 prev_max = lo
33 for b in range(count):
34 if bucket_min[b] == float("inf"):
35 continue
36 best = max(best, bucket_min[b] - prev_max)
37 prev_max = bucket_max[b]
38 
39 return best
Invariant: Bucket b holds exactly the values in [lo + b * width, lo + (b + 1) * width), and width is at most the smallest possible maximum gap.

Bucket Sort drops each value straight into the bucket that covers its range, b = (x - lo) // width, so placing a value needs no comparison with other values and the buckets come out in order. The key can be any bounded number: bucket by value range to find gaps, or by count to order items by frequency. For the largest gap between neighbours, keep only bucket_min[b] and bucket_max[b], walk the buckets once, and measure bucket_min[b] - prev_max, where prev_max is the largest value of the last non-empty bucket. That is enough because of the pigeonhole principle: n values from lo to hi leave n - 1 gaps that add up to hi - lo, so the largest gap is at least their average, and being a whole number it is at least width whenever hi > lo. Two values in the same bucket differ by at most width - 1, so the maximum gap is never inside one bucket: it always runs from one bucket's maximum to a later bucket's minimum, and the values in between never matter. The trap is the floor of 1: when hi - lo < n - 1, for example with repeated values, (hi - lo) // (n - 1) is 0 and (x - lo) // width divides by zero, so write width = max(1, (hi - lo) // (n - 1)).

See it run

Bucket Sort, step by step

x - · b - · prev_max - · best 0

x 1 · b 0 · prev_max - · best 0

x 2 · b 0 · prev_max - · best 0

x 10 · b 2 · prev_max - · best 0

x 4 · b 0 · prev_max - · best 0

x 25 · b 6 · prev_max - · best 0

x 7 · b 1 · prev_max - · best 0

x - · b 0 · prev_max 1 · best 0

x - · b 0 · prev_max 4 · best 0

x - · b 1 · prev_max 4 · best 3

x - · b 1 · prev_max 7 · best 3

x - · b 2 · prev_max 7 · best 3

x - · b 2 · prev_max 10 · best 3

x - · b 3 · prev_max 10 · best 3

x - · b 4 · prev_max 10 · best 3

x - · b 5 · prev_max 10 · best 3

x - · b 6 · prev_max 10 · best 15

x - · b 6 · prev_max 10 · best 15

x - · b 6 · prev_max 25 · best 15

x - · b 6 · prev_max 25 · best 15 · answer 15

Each column is one bucket. The top row is bucket_min, the middle row is bucket_max, and the bottom row is the gap measured when the walk reaches that bucket.

  • put the bucket that receives the value x
  • gap the bucket whose minimum is measured against prev_max
  • prev_max the bucket whose maximum becomes prev_max
  • empty a bucket with no values, skipped in the walk

Start: lo = 1, hi = 25, n = 6, so width = max(1, (25 - 1) // 5) = 4. That makes 7 buckets.

x = 1, so b = (1 - 1) // 4 = 0. bucket_min[0] = 1 and bucket_max[0] = 1.

x = 2, so b = (2 - 1) // 4 = 0. bucket_min[0] = 1 and bucket_max[0] = 2.

x = 10, so b = (10 - 1) // 4 = 2. bucket_min[2] = 10 and bucket_max[2] = 10.

x = 4, so b = (4 - 1) // 4 = 0. bucket_min[0] = 1 and bucket_max[0] = 4.

x = 25, so b = (25 - 1) // 4 = 6. bucket_min[6] = 25 and bucket_max[6] = 25.

x = 7, so b = (7 - 1) // 4 = 1. bucket_min[1] = 7 and bucket_max[1] = 7.

gap = bucket_min[0] - prev_max = 1 - 1 = 0. best is now 0.

prev_max = bucket_max[0] = 4.

gap = bucket_min[1] - prev_max = 7 - 4 = 3. best is now 3.

prev_max = bucket_max[1] = 7.

gap = bucket_min[2] - prev_max = 10 - 7 = 3. best is now 3.

prev_max = bucket_max[2] = 10.

Bucket 3 is empty, so it is skipped. prev_max stays 10.

Bucket 4 is empty, so it is skipped. prev_max stays 10.

Bucket 5 is empty, so it is skipped. prev_max stays 10.

gap = bucket_min[6] - prev_max = 25 - 10 = 15. best is now 15.

Trap: bucket 5 is empty, so bucket_max[5] is -inf and the gap would be inf. prev_max = 10 comes from bucket 2, the last bucket that was not empty.

prev_max = bucket_max[6] = 25.

Done: the largest gap between neighbours is 15.

bucket_min
b0inf
b1inf
b2inf
b3inf
b4inf
b5inf
b6inf
bucket_max
-inf
-inf
-inf
-inf
-inf
-inf
-inf
gap
1 / 20
Gotchas1 of 4

Gotcha 1 of 4

  1. 1
    The trap

    Gotcha 1: Computing width without the floor of 1

    Mistake
    width = (hi - lo) // (n - 1)
    Fix
    width = max(1, (hi - lo) // (n - 1))

    What happens: With repeated values, hi - lo can be smaller than n - 1. Then (hi - lo) // (n - 1) is 0. The next division, (hi - lo) // width, divides by zero. max(1, ...) keeps every bucket at least one value wide.

    Example: nums = [1, 1, 1, 4, 2]
    Mistake: raises ZeroDivisionErrorFix: returns 2
  2. 2

    Gotcha 2: Leaving out the check if n < 2

    Mistake
    n = len(nums)
    Fix
    n = len(nums)
    if n < 2:
        return 0

    What happens: With one value, there is no neighbour and no gap, so the answer is 0. Without the check, the code goes on to compute the width. n - 1 is 0 there, so (hi - lo) // (n - 1) divides by zero.

    Example: nums = [10]
    Mistake: raises ZeroDivisionErrorFix: returns 0
  3. 3

    Gotcha 3: Rounding the width up with + 1

    Mistake
    width = (hi - lo) // (n - 1) + 1
    Fix
    width = max(1, (hi - lo) // (n - 1))

    What happens: The largest gap is at least (hi - lo) / (n - 1), the average gap. A bucket must be no wider than that, so the largest gap can never be inside one bucket. With + 1, a bucket can be wider. Two values can then share a bucket, and the gap between them is never measured.

    Example: nums = [6, 18]
    Mistake: returns 0Fix: returns 12
  4. 4

    Gotcha 4: Comparing with bucket_max[b - 1] instead of prev_max

    Mistake
    best = max(best, bucket_min[b] - bucket_max[b - 1])
    Fix
    best = max(best, bucket_min[b] - prev_max)
    prev_max = bucket_max[b]

    What happens: The largest gap often runs across empty buckets. The value just below bucket_min[b] is then in an earlier bucket, not in bucket b - 1. An empty bucket_max[b - 1] is -inf, and the difference becomes inf. prev_max keeps the maximum of the last bucket that was not empty.

    Example: nums = [1, 2, 10]
    Mistake: returns infFix: returns 8

Deep Dive

The procedure: compute the bucket from the value

A bucket is a place that holds every value inside one range. Bucket Sort puts each value into its bucket with one formula, b = (x - lo) // width. The formula uses only x, lo and width. It never compares x with another value.

The buckets are laid out in order. Bucket 0 holds the smallest values and the last bucket holds the largest. So after the placing loop, the values are already ordered between buckets. Only the order inside one bucket is unknown.

The key can be any bounded number. In maximum_gap the key is the value itself, cut into ranges of width numbers. In Top K Frequent Elements (LC 347) and Sort Characters By Frequency (LC 451) the key is a count. A count is never more than the input length, so buckets[count] can hold every item with that count.

For example, take nums = [1, 1, 1, 2, 2, 3] and k = 2. The counts are 3 for the value 1, 2 for the value 2 and 1 for the value 3. buckets[3] holds [1], buckets[2] holds [2] and buckets[1] holds [3]. Reading buckets from the last index down gives 1, then 2, so the answer is [1, 2].

Maximum Gap, traced on six numbers

Take nums = [1, 2, 10, 4, 25, 7]. Here n = 6, lo = 1 and hi = 25. So width = max(1, 24 // 5) = 4, and count = 24 // 4 + 1 = 7 buckets.

The placing loop gives these buckets. Bucket 0 holds 1, 2 and 4, so its minimum is 1 and its maximum is 4. Bucket 1 holds 7. Bucket 2 holds 10. Buckets 3, 4 and 5 stay empty. Bucket 6 holds 25.

The walk starts with prev_max = lo = 1. Bucket 0 gives a gap of 1 - 1 = 0. Bucket 1 gives 7 - 4 = 3, and bucket 2 gives 10 - 7 = 3. The board marks buckets 3, 4 and 5 as skipped.

Bucket 6 gives 25 - 10 = 15. The last non-empty bucket before it is bucket 2, not bucket 5. This is the trap on the board: bucket_max[5] is -inf, so the formula with bucket_max[b - 1] would give inf. The variable prev_max is 10 and gives the right gap.

The answer is 15. On the LeetCode example [3, 6, 9, 1] the same code returns 3.

Why the answer is never inside one bucket

There are n values, so there are n - 1 gaps between sorted neighbours. The gaps add up to hi - lo. The largest gap is at least the average, (hi - lo) / (n - 1). A gap is a whole number, so the largest gap is at least (hi - lo) // (n - 1), which is width, whenever hi > lo. This is the pigeonhole principle.

A bucket covers width whole numbers, so two values in one bucket differ by at most width - 1. That is smaller than width. So no pair inside one bucket can be the largest gap. The largest gap always starts at the maximum of one non-empty bucket and ends at the minimum of a later non-empty bucket. The other values in a bucket never matter, so each bucket keeps only bucket_min and bucket_max.

The first trap is width = (hi - lo) // (n - 1) with no floor of 1. For nums = [1, 1, 1, 4, 2], hi - lo = 3 and n - 1 = 4, so the result is 0 and the next division raises a ZeroDivisionError. With max(1, ...), width = 1 and the answer is 2. The second trap is a width rounded up with + 1. For nums = [6, 18] the width becomes 13, both values share bucket 0, and the code returns 0 instead of 12.

Cost, and when not to use buckets

The placing loop does O(n) work and the walk visits every bucket once. The number of buckets is (hi - lo) // width + 1, which is less than 2n. So the time is O(n) and the space is O(n). Sorting first costs O(n log n).

Buckets need the range to be known before the data is placed. If the values arrive one at a time and the answer is needed after each arrival, as in Kth Largest Element in a Stream (LC 703), there is no range to lay out. Use a heap there.

If every value is a small whole number, one slot per exact value is enough and no range is needed. That is the next card, Counting Sort.

60-Second Pitch

"Sorting costs O(n log n), so I use buckets. There are n values and n - 1 gaps that add up to hi - lo, so the largest gap is at least (hi - lo) // (n - 1). I make each bucket that wide, with a floor of 1. Each value goes to the bucket (x - lo) // width, and each bucket keeps only its minimum and maximum. Two values in one bucket are closer than the width, so the answer is never inside a bucket. I walk the buckets in order, skip the empty ones, and measure each non-empty bucket's minimum against prev_max. The time is O(n) and the space is O(n). The two traps are the floor of 1 on width, and using prev_max instead of the previous bucket's maximum."

Common Questions

Why is it enough to keep only the minimum and maximum of each bucket?

Two values in one bucket differ by at most width - 1, and the largest gap is at least width. So the largest gap is never between two values of one bucket. It always runs from the maximum of one bucket to the minimum of a later bucket.

What is the pigeonhole argument here?

There are n - 1 gaps between sorted neighbours, and they add up to hi - lo. The largest of them is at least the average. If every bucket is narrower than the average gap, two neighbours cannot be in the same bucket and span the largest gap.

What happens when all values are equal?

Then hi - lo is 0, so (hi - lo) // (n - 1) is 0. The floor gives width = 1 and count = 1. All values fall in bucket 0, and the walk measures a gap of 0.

How does Top K Frequent Elements (LC 347) use buckets?

Count the values with a dictionary. A count is at most len(nums), so make a list buckets with len(nums) + 1 entries and put each value in buckets[count]. Read the list from the last index down until k values are collected. The time is O(n).

When would you use a heap or a sort instead?

Use a heap when values arrive one at a time and the range is not known, as in Kth Largest Element in a Stream (LC 703). Use a sort when n is small and the code must be short. Buckets win when the range is known and the limit needs O(n).

SWE Reasoning

Spot it

  • The values lie in a known, bounded range, and a sort at O(n log n) is too slow for the limit.
  • The question asks for the largest gap between neighbours in sorted order, as in Maximum Gap (LC 164), but the array may not be sorted.
  • The key is a count that is never more than n, and the items must come out ordered by it, as in Sort Characters By Frequency (LC 451) or Top K Frequent Elements (LC 347).

The invariant, step by step

  1. lo and hi are the smallest and largest value. width = max(1, (hi - lo) // (n - 1)) is a number that is at most the smallest possible largest gap.
  2. Bucket b holds exactly the values in [lo + b * width, lo + (b + 1) * width).
  3. Each value goes to bucket b = (x - lo) // width. The bucket keeps only its smallest and largest value.
  4. The walk visits the buckets in order. It skips empty buckets and keeps prev_max, the largest value of the last non-empty bucket.
  5. The gap bucket_min[b] - prev_max is measured only across buckets, and the largest of these is the answer.

Traps

  • No floor of 1 on width. With repeated values, (hi - lo) // (n - 1) is 0 and the next division raises a ZeroDivisionError.
  • No check for n < 2. With one value, n - 1 is 0 and the division raises a ZeroDivisionError. The answer is 0.
  • A width rounded up with + 1. Two values can then share a bucket, and the gap between them is never measured. [6, 18] returns 0 instead of 12.
  • Using bucket_max[b - 1] instead of prev_max. An empty previous bucket has the maximum -inf, and the gap becomes inf.

Practice

Canonical LeetCode:Maximum Gap (LC 164)Sort Characters By Frequency (LC 451)Top K Frequent Elements (LC 347)
© 2026 Hi👋WebEnterprise. All rights reserved.
Sitemap•llms.txt•