Problem overview
minimumAbsDifference takes an IntArray of distinct integers and finds the smallest difference between any two of its elements. It returns every pair [a, b] with a < b and b - a equal to that smallest difference, as a List<IntArray> in ascending order.
For [4, 2, 1, 3] the smallest difference is 1, and the pairs are [1, 2], [2, 3] and [3, 4]. For [1, 3, 6, 10, 15] the smallest difference is 2, and the only pair is [1, 3].
Approach
Comparing every pair of elements is correct, but it takes O(n^2) time, which is too slow for 100,000 elements.
Sorting makes the problem local. If two elements x < z have a third element y between them in sorted order, then y - x and z - y are both smaller than z - x. So a pair with something between it can never be the closest pair, and the minimum difference always occurs between neighbours. Only n - 1 neighbouring pairs need checking.
The sort also solves the output order. Pairs taken from left to right in a sorted array are already in ascending order, and each has a < b because the values are distinct.
The scan keeps the smallest gap seen so far and the list of pairs with that gap. A smaller gap replaces the list; an equal gap adds to it.
Step by step
- Sort
numsin place withsort(). - Set the starting minimum. Declare
var minDiffasnums[1] - nums[0], the gap of the first pair, which is a real gap in the array. Also declareval minDiffPairsas an emptymutableListOf<IntArray>(). It is avalbecause the list object stays the same while its contents change. - Loop over neighbouring pairs with
for (i in 1 until nums.size). Eachistands for the pairnums[i - 1],nums[i]. - Compute the gap:
val curDiff = nums[i] - nums[i - 1]. Because the array is sorted, it is never negative. - If
curDiff < minDiff, a new record has been found. SetminDifftocurDiff,clear()the list, because all earlier pairs have a larger gap, and add the new pair asintArrayOf(nums[i - 1], nums[i]). - Otherwise, if
curDiff == minDiff, add the pair to the list without clearing. - Return
minDiffPairs.
Note that on the first iteration curDiff equals the starting minDiff, so the first pair is added by the else if branch.
The solution in Kotlin
class Solution {
fun minimumAbsDifference(nums: IntArray): List<IntArray> {
nums.sort()
var minDiff = nums[1] - nums[0]
val minDiffPairs = mutableListOf<IntArray>()
for (i in 1 until nums.size) {
val curDiff = nums[i] - nums[i - 1]
if (curDiff < minDiff) {
minDiff = curDiff
minDiffPairs.clear()
minDiffPairs.add(intArrayOf(nums[i - 1], nums[i]))
} else if (curDiff == minDiff) {
minDiffPairs.add(intArrayOf(nums[i - 1], nums[i]))
}
}
return minDiffPairs
}
}
Kotlin details worth knowing:
- The return type is
List<IntArray>, a read-only view. Inside the function the object is aMutableList, which is whyclear()andadd()are allowed there; returning it as aListis fine. - Each pair is a fresh
IntArraymade withintArrayOf. Printing anIntArraydirectly shows a reference-style text, so usetoList()orjoinToString()to display it. if/else ifis a statement here, with no value used.- The subtraction can't overflow for the lesson's range of -1,000,000 to 1,000,000, since differences stay within 2,000,000. With values near
Int.MIN_VALUEandInt.MAX_VALUEthe difference could wrap around, and you would useLong.
Tracing [3, 8, -10, 23, 19, -4, -14, 27]: after sorting it is [-14, -10, -4, 3, 8, 19, 23, 27], and the gaps between neighbours are 4, 6, 7, 5, 11, 4 and 4. The starting minimum is 4, and the first pair [-14, -10] is added. The gap 6 and the following gaps 7, 5 and 11 change nothing. The gaps 4 at [19, 23] and [23, 27] equal the minimum, so both are appended.
The check for a smaller gap comes first and the equal check second. If both used <= in one branch, you would need to clear the list only for strictly smaller gaps, which is exactly why the lesson splits the cases.
Complexity
- Time:
O(n log n)for the sort andO(n)for the scan. - Space:
O(1)extra for the sort and variables, not counting the returned list. In the worst case, such as[1, 2, 3, 4], it holdsn - 1pairs.
Edge cases and common mistakes
- Two elements. There is one pair, and it is returned.
- Not clearing the list. Without
clear()on a new minimum, pairs with a larger gap would stay in the result. - Checking only the first minimum. Several pairs can share the minimum gap, as in
[-14, -10],[19, 23]and[23, 27], so keep going after finding one. - Skipping the sort. Then neighbours in the input are not neighbours in value, and the pairs would not be in ascending order either.
- Starting
minDiffat 0. Distinct values never have a gap of 0, so nothing would ever be smaller or equal, and the result would stay empty. - Declaring
minDiffwithval. It is reassigned when a smaller gap appears, so Kotlin reports an error unless it is avar. The list, by contrast, is never reassigned, sovalis correct for it. - Reusing one pair array. Add a new
intArrayOf(...)each time. Adding the same array and changing it later would change every stored pair.
Testing
[4, 2, 1, 3]returns[[1, 2], [2, 3], [3, 4]].[1, 3, 6, 10, 15]returns[[1, 3]].[3, 8, -10, 23, 19, -4, -14, 27]returns[[-14, -10], [19, 23], [23, 27]].