Problem overview
The function minimumAbsDifference receives a vector of distinct integers, nums. It must find the smallest absolute difference between any two elements and return every pair [a, b], with a < b, whose difference equals it. The pairs must be listed in ascending order.
For nums = [4, 2, 1, 3], the smallest difference is 1, reached by [1, 2], [2, 3] and [3, 4]. For [3, 8, -10, 23, 19, -4, -14, 27] the smallest difference is 4, and the answer is [[-14, -10], [19, 23], [23, 27]]. The vector can hold up to 100,000 numbers.
Approach: sort, then compare neighbours
The brute-force solution checks every pair, which is O(n²). For 100,000 elements that is about five billion pairs, too many.
Sorting makes it much cheaper, because of one observation: in a sorted array, the closest pair is always adjacent. Suppose x < y < z in sorted order. Then z - x is larger than both y - x and z - y, so a pair with another element between them can't be a minimum. Only the n - 1 neighbouring pairs need checking.
Sorting also solves the output requirements. In sorted order, each neighbouring pair is already [smaller, larger], and the pairs appear in increasing order, so no extra sorting of the result is needed. The numbers are distinct, so all differences are positive, and no absolute value is needed either.
The scan keeps the best gap seen so far, minDiff, and a list of the pairs that achieve it. A smaller gap replaces the whole list. An equal gap is appended. A larger gap is ignored.
On [4, 2, 1, 3] the sorted vector is [1, 2, 3, 4]. All three gaps are 1, so all three neighbouring pairs are kept.
Step by step
- Sort ascending in place. Call
sortonnums.begin()tonums.end(). After this, the closest pair must be adjacent. - Seed the best gap. Set
minDifftonums[1] - nums[0], and declare an emptyminDiffPairs. The constraints guarantee at least two elements. - Loop over neighbours. The variable
istarts at 1, so thati - 1is always a valid index. - Measure the gap.
curDiffisnums[i] - nums[i - 1]. It is never negative in sorted order. - Check for a new record. Test
curDiff < minDiff. - Reset on a record. Set
minDifftocurDiff, and assign a nested brace list holding only the pair{nums[i - 1], nums[i]}tominDiffPairs. Assigning a brace list to a vector replaces its whole contents, which clears the old pairs in one step. - Check for a tie. An
else iftestscurDiff == minDiff. - Append on a tie. Push back
{nums[i - 1], nums[i]}. - Return
minDiffPairsafter the loop.
The solution in C++
class Solution {
public:
vector<vector<int>> minimumAbsDifference(vector<int>& nums) {
sort(nums.begin(), nums.end());
int minDiff = nums[1] - nums[0];
vector<vector<int>> minDiffPairs;
for (int i = 1; i < nums.size(); i++) {
int curDiff = nums[i] - nums[i - 1];
if (curDiff < minDiff) {
minDiff = curDiff;
minDiffPairs = {{nums[i - 1], nums[i]}};
} else if (curDiff == minDiff) {
minDiffPairs.push_back({nums[i - 1], nums[i]});
}
}
return minDiffPairs;
}
};
The C++ details worth knowing:
- The first pair is not lost.
minDiffPairsstarts empty, althoughminDiffalready holds the gap betweennums[0]andnums[1]. The loop starts ati = 1, which measures that very same pair, findscurDiff == minDiffand appends it. sortchanges the caller's vector. The parameter is a reference, so after the call the caller's vector is sorted. Make a copy first if the original order matters.- Brace-list assignment.
minDiffPairs = {{a, b}}builds one inner vector{a, b}and makes it the only element.push_back({a, b})builds the inner vector and appends it. - Subtraction and overflow. With the constraint of values within one million in absolute value,
nums[i] - nums[i - 1]is at most two million. If values could reach theintlimits, the subtraction could overflow, and signed overflow is undefined behaviour. Usinglong longfor the differences would avoid that. - Unsigned size.
nums.size()is unsigned. The loop starts at 1 and the vector has at least two elements, so nothing wraps. On an empty vector,nums.size() - 1would wrap to a huge number, a classic trap with unsigned sizes.
Complexity
Time: O(n log n). Sorting costs O(n log n) and the scan costs O(n), so sorting dominates.
Space: O(n) for the result. The sort works in place, so the extra memory is the list of pairs. In the worst case, such as an evenly spaced sequence, every neighbouring pair is returned, which is n - 1 pairs.
Edge cases and common mistakes
- Two elements.
[5, 1]sorts to[1, 5]and returns[[1, 5]]. - Evenly spaced values.
[1, 2, 3, 4]returns all three neighbouring pairs. - Negative numbers. Sorting handles them with no special case:
[-14, -10]is a valid pair. - Checking only pairs involving the minimum. The best pair can be anywhere in the sorted order.
- Forgetting to clear on a new record. Pairs from a larger gap would remain in the result.
- Using two
ifstatements. After a new record,minDiffequalscurDiff, so a second plainifwould append the same pair again. - Comparing non-neighbours. Looking at
nums[i] - nums[i - 2]or any wider gap is wasted work. A pair with an element between them is never a minimum.
Testing
[4, 2, 1, 3]gives[[1, 2], [2, 3], [3, 4]].[1, 3, 6, 10, 15]gives[[1, 3]].[3, 8, -10, 23, 19, -4, -14, 27]gives[[-14, -10], [19, 23], [23, 27]].[5, 1]gives[[1, 5]].[1, 2, 10, 11]gives[[1, 2], [10, 11]].