164. Maximum Gap
Problem Description
Given an integer array nums
, you need to find the maximum difference between two successive elements after the array is sorted. If the array contains less than two elements, return 0
.
The challenge is to solve this problem in linear time complexity O(n) and using linear extra space O(n).
For example:
- If
nums = [3, 6, 9, 1]
, after sorting it becomes[1, 3, 6, 9]
. The successive differences are3-1=2
,6-3=3
, and9-6=3
. The maximum gap is3
. - If
nums = [10]
, there's only one element, so return0
.
The key insight is using bucket sort principles. Since we need linear time complexity, we cannot use comparison-based sorting algorithms like quicksort or mergesort which have O(n log n) complexity.
The algorithm works by:
- Finding the minimum and maximum values in the array
- Creating buckets with calculated bucket size based on the range
(max - min) / (n - 1)
- Distributing elements into buckets and tracking the min and max values in each bucket
- The maximum gap must occur between the maximum element of one bucket and the minimum element of the next non-empty bucket (not within the same bucket due to the pigeonhole principle)
- Iterating through buckets to find the maximum gap between successive non-empty buckets
This approach ensures that the maximum gap is found efficiently without explicitly sorting the entire array.
Intuition
When we need to find the maximum gap between successive elements in a sorted array in linear time, we can't use traditional sorting algorithms since they take O(n log n) time. This constraint pushes us to think about non-comparison based sorting approaches.
The key observation is that if we have n
numbers and we know the minimum and maximum values, we can estimate what the minimum possible maximum gap would be. This minimum possible gap occurs when all numbers are evenly distributed, which would be (max - min) / (n - 1)
.
Why is this important? Because the actual maximum gap must be at least this value or larger. This gives us a clever idea: if we create buckets of size (max - min) / (n - 1)
, then by the pigeonhole principle, at least one bucket must be empty (unless all numbers are evenly distributed).
Here's the crucial insight: since each bucket has a size equal to the minimum possible maximum gap, the maximum gap cannot occur between two numbers within the same bucket. It must occur between numbers in different buckets - specifically between the maximum value of one bucket and the minimum value of the next non-empty bucket.
This transforms our problem from "sort all elements and find the maximum gap" to "distribute elements into buckets, track min/max of each bucket, and find the maximum gap between consecutive non-empty buckets". We only need to track the minimum and maximum values in each bucket, not all the elements, which keeps our space complexity linear.
The bucket approach works because:
- Elements in the same bucket are close to each other (within the bucket size range)
- The gap between buckets is guaranteed to be at least the bucket size
- We can process all elements in one pass to fill buckets (O(n))
- We can scan through buckets once to find the maximum gap (O(n))
Solution Approach
Let's walk through the implementation step by step:
-
Handle edge case: If the array has less than 2 elements, return
0
immediately since there's no gap to calculate. -
Find the range: Calculate the minimum (
mi
) and maximum (mx
) values in the array. This helps us determine the range of values we're working with. -
Calculate bucket size: The bucket size is computed as
max(1, (mx - mi) // (n - 1))
. We usemax(1, ...)
to ensure the bucket size is at least 1 to avoid division by zero when all elements are the same. -
Determine bucket count: The number of buckets needed is
(mx - mi) // bucket_size + 1
. This ensures we have enough buckets to cover the entire range. -
Initialize buckets: Create
bucket_count
buckets, where each bucket is represented as[min_value, max_value]
. Initially, set them to[inf, -inf]
to indicate empty buckets. -
Distribute elements into buckets: For each number
v
in the array:- Calculate which bucket it belongs to:
i = (v - mi) // bucket_size
- Update the bucket's minimum value:
buckets[i][0] = min(buckets[i][0], v)
- Update the bucket's maximum value:
buckets[i][1] = max(buckets[i][1], v)
- Calculate which bucket it belongs to:
-
Find maximum gap:
- Initialize
ans = 0
to track the maximum gap - Initialize
prev = inf
to track the maximum value of the previous non-empty bucket - Iterate through all buckets:
- Skip empty buckets (where
curmin > curmax
) - Calculate the gap between current bucket's minimum and previous bucket's maximum:
curmin - prev
- Update the maximum gap:
ans = max(ans, curmin - prev)
- Update
prev
to current bucket's maximum for the next iteration
- Skip empty buckets (where
- Initialize
-
Return result: The variable
ans
now contains the maximum gap between successive elements.
The algorithm achieves O(n) time complexity through single-pass operations: finding min/max (O(n)), distributing elements (O(n)), and scanning buckets (O(n)). The space complexity is also O(n) for storing the buckets.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the algorithm with nums = [3, 6, 9, 1]
:
Step 1: Find min and max
min = 1
,max = 9
- Range =
9 - 1 = 8
Step 2: Calculate bucket size
- We have
n = 4
elements - Bucket size =
(max - min) / (n - 1) = 8 / 3 = 2.67
, rounded down to2
- This means each bucket covers a range of 2 units
Step 3: Create buckets
- Number of buckets =
(9 - 1) / 2 + 1 = 5
buckets - Initialize 5 buckets:
[[inf, -inf], [inf, -inf], [inf, -inf], [inf, -inf], [inf, -inf]]
Step 4: Distribute elements into buckets
- Element
3
: bucket index =(3 - 1) / 2 = 1
โ Bucket 1:[3, 3]
- Element
6
: bucket index =(6 - 1) / 2 = 2
โ Bucket 2:[6, 6]
- Element
9
: bucket index =(9 - 1) / 2 = 4
โ Bucket 4:[9, 9]
- Element
1
: bucket index =(1 - 1) / 2 = 0
โ Bucket 0:[1, 1]
Buckets after distribution:
- Bucket 0:
[1, 1]
(contains value 1) - Bucket 1:
[3, 3]
(contains value 3) - Bucket 2:
[6, 6]
(contains value 6) - Bucket 3:
[inf, -inf]
(empty) - Bucket 4:
[9, 9]
(contains value 9)
Step 5: Find maximum gap between consecutive non-empty buckets
- Start with
prev = inf
,max_gap = 0
- Bucket 0
[1, 1]
: First non-empty bucket, setprev = 1
- Bucket 1
[3, 3]
: Gap =3 - 1 = 2
, updatemax_gap = 2
, setprev = 3
- Bucket 2
[6, 6]
: Gap =6 - 3 = 3
, updatemax_gap = 3
, setprev = 6
- Bucket 3: Empty, skip
- Bucket 4
[9, 9]
: Gap =9 - 6 = 3
,max_gap
remains3
Result: The maximum gap is 3
, which matches the expected result when sorting [1, 3, 6, 9]
and finding the largest difference between consecutive elements.
Notice how Bucket 3 is empty, demonstrating that the maximum gap (3) indeed occurs between buckets (from Bucket 2's max to Bucket 4's min), not within any single bucket. This validates our bucket size calculation and the pigeonhole principle at work.
Solution Implementation
1from typing import List
2from math import inf
3
4
5class Solution:
6 def maximumGap(self, nums: List[int]) -> int:
7 """
8 Find the maximum gap between successive elements in sorted form.
9 Uses bucket sort approach with pigeonhole principle.
10
11 Args:
12 nums: List of integers
13
14 Returns:
15 Maximum gap between successive elements when sorted
16 """
17 n = len(nums)
18
19 # Base case: need at least 2 elements for a gap
20 if n < 2:
21 return 0
22
23 # Find the minimum and maximum values in the array
24 min_val = min(nums)
25 max_val = max(nums)
26
27 # Calculate bucket size using pigeonhole principle
28 # With n numbers and n-1 gaps, at least one gap must be >= (max-min)/(n-1)
29 bucket_size = max(1, (max_val - min_val) // (n - 1))
30
31 # Calculate number of buckets needed
32 bucket_count = (max_val - min_val) // bucket_size + 1
33
34 # Initialize buckets with [min_value, max_value] for each bucket
35 # inf and -inf represent empty buckets
36 buckets = [[inf, -inf] for _ in range(bucket_count)]
37
38 # Place each number into its corresponding bucket
39 for value in nums:
40 # Determine which bucket this value belongs to
41 bucket_index = (value - min_val) // bucket_size
42
43 # Update the minimum and maximum values in this bucket
44 buckets[bucket_index][0] = min(buckets[bucket_index][0], value)
45 buckets[bucket_index][1] = max(buckets[bucket_index][1], value)
46
47 # Find the maximum gap by comparing adjacent non-empty buckets
48 max_gap = 0
49 prev_max = inf # Track the maximum value of the previous non-empty bucket
50
51 for current_min, current_max in buckets:
52 # Skip empty buckets (where min > max due to initialization)
53 if current_min > current_max:
54 continue
55
56 # Calculate gap between current bucket's min and previous bucket's max
57 max_gap = max(max_gap, current_min - prev_max)
58
59 # Update previous maximum for next iteration
60 prev_max = current_max
61
62 return max_gap
63
1class Solution {
2 public int maximumGap(int[] nums) {
3 int n = nums.length;
4
5 // If array has less than 2 elements, no gap exists
6 if (n < 2) {
7 return 0;
8 }
9
10 // Initialize constants and find min/max values in the array
11 final int INFINITY = 0x3f3f3f3f;
12 int minValue = INFINITY;
13 int maxValue = -INFINITY;
14
15 for (int num : nums) {
16 minValue = Math.min(minValue, num);
17 maxValue = Math.max(maxValue, num);
18 }
19
20 // Calculate bucket size using pigeonhole principle
21 // The minimum possible maximum gap is (max - min) / (n - 1)
22 int bucketSize = Math.max(1, (maxValue - minValue) / (n - 1));
23 int bucketCount = (maxValue - minValue) / bucketSize + 1;
24
25 // Initialize buckets - each bucket stores [min, max] values
26 int[][] buckets = new int[bucketCount][2];
27 for (int[] bucket : buckets) {
28 bucket[0] = INFINITY; // minimum value in bucket
29 bucket[1] = -INFINITY; // maximum value in bucket
30 }
31
32 // Distribute numbers into buckets based on their values
33 for (int num : nums) {
34 int bucketIndex = (num - minValue) / bucketSize;
35 buckets[bucketIndex][0] = Math.min(buckets[bucketIndex][0], num);
36 buckets[bucketIndex][1] = Math.max(buckets[bucketIndex][1], num);
37 }
38
39 // Find maximum gap by comparing adjacent non-empty buckets
40 int previousMax = INFINITY;
41 int maxGap = 0;
42
43 for (int[] bucket : buckets) {
44 // Skip empty buckets (where min > max after initialization)
45 if (bucket[0] > bucket[1]) {
46 continue;
47 }
48
49 // Calculate gap between current bucket's min and previous bucket's max
50 maxGap = Math.max(maxGap, bucket[0] - previousMax);
51 previousMax = bucket[1];
52 }
53
54 return maxGap;
55 }
56}
57
1using pii = pair<int, int>;
2
3class Solution {
4public:
5 const int INF = 0x3f3f3f3f;
6
7 int maximumGap(vector<int>& nums) {
8 int n = nums.size();
9
10 // Edge case: less than 2 elements
11 if (n < 2) {
12 return 0;
13 }
14
15 // Find minimum and maximum values in the array
16 int minValue = INF;
17 int maxValue = -INF;
18 for (int value : nums) {
19 minValue = min(minValue, value);
20 maxValue = max(maxValue, value);
21 }
22
23 // Calculate bucket size using pigeonhole principle
24 // The maximum gap must be at least (maxValue - minValue) / (n - 1)
25 int bucketSize = max(1, (maxValue - minValue) / (n - 1));
26 int bucketCount = (maxValue - minValue) / bucketSize + 1;
27
28 // Initialize buckets: each bucket stores (min, max) values
29 vector<pii> buckets(bucketCount, {INF, -INF});
30
31 // Distribute numbers into buckets
32 for (int value : nums) {
33 int bucketIndex = (value - minValue) / bucketSize;
34 buckets[bucketIndex].first = min(buckets[bucketIndex].first, value);
35 buckets[bucketIndex].second = max(buckets[bucketIndex].second, value);
36 }
37
38 // Find maximum gap by comparing adjacent non-empty buckets
39 int maxGap = 0;
40 int previousMax = INF;
41
42 for (auto [currentMin, currentMax] : buckets) {
43 // Skip empty buckets
44 if (currentMin > currentMax) {
45 continue;
46 }
47
48 // Calculate gap between current bucket's min and previous bucket's max
49 maxGap = max(maxGap, currentMin - previousMax);
50 previousMax = currentMax;
51 }
52
53 return maxGap;
54 }
55};
56
1// Type alias for a pair of integers [min, max]
2type IntPair = [number, number];
3
4// Constant representing infinity
5const INF = 0x3f3f3f3f;
6
7function maximumGap(nums: number[]): number {
8 const n = nums.length;
9
10 // Edge case: less than 2 elements
11 if (n < 2) {
12 return 0;
13 }
14
15 // Find minimum and maximum values in the array
16 let minValue = INF;
17 let maxValue = -INF;
18 for (const value of nums) {
19 minValue = Math.min(minValue, value);
20 maxValue = Math.max(maxValue, value);
21 }
22
23 // Calculate bucket size using pigeonhole principle
24 // The maximum gap must be at least (maxValue - minValue) / (n - 1)
25 const bucketSize = Math.max(1, Math.floor((maxValue - minValue) / (n - 1)));
26 const bucketCount = Math.floor((maxValue - minValue) / bucketSize) + 1;
27
28 // Initialize buckets: each bucket stores [min, max] values
29 const buckets: IntPair[] = Array(bucketCount).fill(null).map(() => [INF, -INF]);
30
31 // Distribute numbers into buckets
32 for (const value of nums) {
33 const bucketIndex = Math.floor((value - minValue) / bucketSize);
34 buckets[bucketIndex][0] = Math.min(buckets[bucketIndex][0], value);
35 buckets[bucketIndex][1] = Math.max(buckets[bucketIndex][1], value);
36 }
37
38 // Find maximum gap by comparing adjacent non-empty buckets
39 let maxGap = 0;
40 let previousMax = minValue; // Initialize with the minimum value
41
42 for (const [currentMin, currentMax] of buckets) {
43 // Skip empty buckets (where min > max indicates empty bucket)
44 if (currentMin > currentMax) {
45 continue;
46 }
47
48 // Calculate gap between current bucket's min and previous bucket's max
49 maxGap = Math.max(maxGap, currentMin - previousMax);
50 previousMax = currentMax;
51 }
52
53 return maxGap;
54}
55
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the input array nums
. This is because:
- Finding the minimum and maximum values takes
O(n)
time - Creating the buckets array takes
O(bucket_count)
time, wherebucket_count โค n
- Distributing all elements into buckets requires one pass through the array, taking
O(n)
time - The final loop through buckets takes
O(bucket_count)
time, which is at mostO(n)
The space complexity is O(n)
. This is because:
- The buckets array has size
bucket_count
, which is calculated as(mx - mi) // bucket_size + 1
- Since
bucket_size = max(1, (mx - mi) // (n - 1))
, thebucket_count
is approximatelyn
in the worst case - Each bucket stores exactly 2 values (min and max), so the total space used is
O(bucket_count) = O(n)
Common Pitfalls
Pitfall 1: Incorrect Bucket Size Calculation
Problem: When all elements in the array are identical (e.g., nums = [1, 1, 1, 1]
), the calculation (max_val - min_val) // (n - 1)
results in 0 // 3 = 0
, which would cause division by zero errors when determining bucket indices.
Solution: Always ensure the bucket size is at least 1 by using max(1, (max_val - min_val) // (n - 1))
. This handles the edge case where all elements are the same, resulting in a maximum gap of 0.
Pitfall 2: Off-by-One Error in Bucket Count
Problem: Calculating bucket count as simply (max_val - min_val) // bucket_size
can miss the last bucket. For example, if the range is 10 and bucket_size is 3, we'd get 3 buckets, but we actually need 4 to cover values at positions 0-2, 3-5, 6-8, and 9-10.
Solution: Add 1 to the bucket count calculation: (max_val - min_val) // bucket_size + 1
. This ensures all values in the range are covered.
Pitfall 3: Incorrect Gap Calculation Between Buckets
Problem: A common mistake is calculating the gap as current_max - prev_max
or current_min - prev_min
, which doesn't give the actual gap between successive elements.
Solution: The correct gap between successive elements is current_min - prev_max
. The gap occurs between the maximum of one bucket and the minimum of the next non-empty bucket.
Pitfall 4: Not Initializing prev_max
Correctly
Problem: Initializing prev_max
to 0 or the minimum value can lead to incorrect gap calculations for the first non-empty bucket.
Solution: Initialize prev_max
to inf
initially, then in the loop, only calculate the gap after encountering the first non-empty bucket. The code handles this by checking max_gap = max(max_gap, current_min - prev_max)
where the first calculation with inf
will be negative and won't affect the result.
Pitfall 5: Forgetting the Pigeonhole Principle
Problem: Attempting to find the maximum gap within buckets rather than between buckets, which defeats the purpose of the bucket sort optimization.
Solution: Remember that by the pigeonhole principle, when we have n-1 gaps and create buckets of size (max-min)/(n-1)
, the maximum gap cannot occur within a single bucketโit must occur between buckets. This is why we only track min and max for each bucket rather than all elements.
Which of the following array represent a max heap?
Recommended Readings
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donโt Miss This!