Problem overview
The function array_diff receives two vectors of integers, first and second. It must find one number from each whose absolute difference is as close to zero as possible, and return those two numbers as a vector, the one from first first. For first = [4, 1, 8] and second = [12, 3, 17] the answer is [4, 3], since |4 - 3| = 1 and no other pair is closer.
The statement asks for O(n log n) time and O(1) extra space.
Approach: sort both, then sweep
Comparing every number of first with every number of second costs O(n · m). Sorting first makes a single pass enough.
Put a pointer i at the start of the sorted first and a pointer j at the start of the sorted second. Look at the pair (first[i], second[j]), record it if its difference is the best so far, and then move one pointer. Which one? The one at the smaller value. Suppose first[i] is smaller. Pairing it with any later element of second would only make the gap larger, because those elements are at least as big as second[j]. So first[i] has already met its best partner, and it can be retired. Moving the pointer at the larger value is the wrong choice, since the gap with the smaller one would then only grow.
Each step retires one element, so the sweep takes at most n + m steps. The sorting dominates, with O(n log n) time.
The example, sorted, is [1, 4, 8] and [3, 12, 17]:
- (1, 3): difference 2, the best so far. 1 is smaller, so
imoves on. - (4, 3): difference 1, a new best. 3 is smaller, so
jmoves on. - (4, 12): difference 8, not better. 4 is smaller, so
imoves on. - (8, 12): difference 4, not better. 8 is smaller, so
imoves on andfirstis finished.
The best pair was [4, 3].
Step by step
- Sort
firstwithfirst.sort(). The parameters are declaredmutin the starter code, which is what allows sorting them in place. - Sort
secondthe same way. The pointer trick needs both in ascending order. - Declare the pointers.
iandjare mutableusizevalues starting at0usize, since vectors are indexed withusize. - Track the best difference.
smallest_diffstarts ati32::MAX, so the first pair examined replaces it. - Prepare the answer.
resultis a mutableVec::new(). Its element type is inferred from the pair assigned to it later. - Loop while
i < first.len() && j < second.len(). When either vector is used up, there are no more pairs to look at. - Measure.
let diff = (first[i] - second[j]).abs();. The parentheses makeabs()apply to the whole subtraction. - Record improvements. If
diff < smallest_diff, updatesmallest_diffand setresult = vec![first[i], second[j]]. The test is strict, so a later pair with the same difference doesn't replace the first one found. - Advance the smaller side. If
first[i] < second[j], doi += 1. - Otherwise advance
j. Theelsebranch coverssecond[j]being smaller or equal. With equal values the difference is 0, already the best possible, so it doesn't matter which pointer moves. - Return. End with
result, with no semicolon.
The solution in Rust
impl Solution {
fn array_diff(mut first: Vec<i32>, mut second: Vec<i32>) -> Vec<i32> {
first.sort();
second.sort();
let mut i = 0usize;
let mut j = 0usize;
let mut smallest_diff = i32::MAX;
let mut result = Vec::new();
while i < first.len() && j < second.len() {
let diff = (first[i] - second[j]).abs();
if diff < smallest_diff {
smallest_diff = diff;
result = vec![first[i], second[j]];
}
if first[i] < second[j] {
i += 1;
} else {
j += 1;
}
}
result
}
}
Rust details worth knowing:
muton parameters.mut first: Vec<i32>means the function owns the vector and may change it. The caller's vector was moved in, so sorting it is invisible to anyone else.- Type inference.
Vec::new()alone doesn't say what it holds. The compiler works it out from the later assignmentvec![first[i], second[j]]and from the return type,Vec<i32>. abs()is a method. There is no free functionabs(x). Because the subtraction is parenthesised, the method applies to its result and not just tosecond[j].- Reassigning
result. Assigning a newvec!drops the old pair and stores the new one. - Stable sort and space.
sort()is a stable sort that may use a temporary buffer. A strictly in-place sort of integers issort_unstable(). For integers the final order is the same.
Complexity
Time: O(n log n). Sorting both vectors costs O(n log n + m log m). The sweep is linear, since each iteration moves one pointer forward, so at most n + m iterations run.
Space: O(1) beyond the sort. The sweep keeps two indices, the best difference and a two-element answer. With sort_unstable() the sort needs no extra buffer either, while sort() can use a temporary one.
Edge cases and common mistakes
- Equal values in both vectors.
[1, 5]and[5, 9]give[5, 5], a difference of 0. - Single elements.
[10]and[3]give[10, 3]. The only pair is the answer. - Empty input. If either vector is empty, the loop never runs and the function returns an empty vector. The lesson's inputs are non-empty.
- Negative numbers.
[-7, 20]and[-3, 30]give[-7, -3]. The absolute value makes the sign irrelevant. - Order of the pair. The element from
firstis always listed first, wherever it sits numerically:[1, 6]and[4, 9]give[6, 4]. - Overflow in the subtraction. The difference is computed in
i32. Withfirst = [-5, 2147483647]andsecond = [-1], the pair (2147483647, -1) wraps in an optimized build to a negative number, soabs()doesn't give a positive value and the wrong pair[2147483647, -1]is returned instead of[-5, -1]. In a debug build the subtraction panics. Computing the difference asi64avoids both. - Moving the wrong pointer. Advancing the larger side skips candidates that could be better.
Testing
[4, 1, 8]and[12, 3, 17]give[4, 3].[1, 5]and[5, 9]give[5, 5].[10]and[3]give[10, 3].[-7, 20]and[-3, 30]give[-7, -3].[1, 6]and[4, 9]give[6, 4].
