Reputation: 43
The original question has a list of unsorted list of 2 integers. To simplify this problem let's just consider the input is 2 sorted arrays of integers and an integer target. Value pair can repeat if there are more than 1 solution pair.
For example: [7,8,14],[5,10,14] target: 20 The solution is [14, 5] as 14 from first array and 5 from second array sums 19 which is closest to 20.
My solution was to loop through both array from beginning to end and compare against a tracked minimum difference and update if new difference is smaller.
But this is brute force. Is there any better solution?
Most solutions I found online was to find the target from the same array, is there any similarities between 2 arrays target problem and 1 array?
Upvotes: 3
Views: 1607
Reputation: 1746
One key insight: Given a pair (x, y) whose sum is higher than the target, that sum is closer than the sum of any pair (x, y'), where y' > y. Conversely, if the sum of (x, y) is lower than the target, that sum is closer than the sum of any pair (x', y) where x' < x.
This yields an algorithm in linear time:
In Java:
private static Pair<Integer, Integer> findClosestSum(List<Integer> X, List<Integer> Y, int target) {
double bestDifference = Integer.MAX_VALUE;
Pair<Integer, Integer> bestPair = null;
int xIndex = 0;
int yIndex = Y.size() - 1;
while (true) {
double sum = X.get(xIndex) + Y.get(yIndex);
double difference = Math.abs(sum - target);
if (difference < bestDifference) {
bestPair = new Pair<>(X.get(xIndex), Y.get(yIndex));
bestDifference = difference;
}
if (sum > target) {
yIndex -= 1;
if (yIndex < 0) {
return bestPair;
}
} else if (sum < target) {
xIndex += 1;
if (xIndex == X.size()) {
return bestPair;
}
} else {
// Perfect match :)
return bestPair;
}
}
}
You can prove this algorithm is exhaustive through the logic in the starting paragraph. For any pair that wasn't visited, there must be a pair including one of its two elements that was visited, and which has a sum strictly closer to the target.
EDIT: If you only want sums which are less than the target (not those which overshoot), the same logic still applies. In the overshoot case, (x, y') is just as invalid as (x, y), and therefore it cannot be a better candidate sum. In this case, only step 2 needs to be modified, to store the sum only if it's the closest non-exceeding sum so far.
Upvotes: 2
Reputation: 43
Thank you for your algorithm, I have implemented my logic. Yes it does need to be the closest pair below target so I have made code changes accordingly. Since the inputs could be duplicates hence I made sure the handle that as well. Also result could be multiple so that is handled as well. Let me know if you find any potential optimization. Here is the code:
public static List<List<Integer>> findClosest(int[] x, int[] y, int target){
List<List<Integer>> result = new ArrayList<List<Integer>>();
int[] pair = new int[2];
int bestDiff = Integer.MIN_VALUE;
int xIndex = 0;
int yIndex = y.length - 1;
//while left doesn't reach left end and right doesn't reach right end
while(xIndex < x.length && yIndex >= 0){
int xValue = x[xIndex];
int yValue = y[yIndex];
int diff = xValue + yValue - target;
//values greater than target, y pointer go right
if(diff > 0){
yIndex--;
while(yIndex > 0 && yValue == y[yIndex - 1]) yIndex--;
}else{//combined == 0 which match target and < 0 which means the sum is less than target
//duplicates result, just add
if(diff == bestDiff){
result.add(Arrays.asList(xValue, yValue));
}
//found better pair, clear array and add new pair
else if(diff > bestDiff){
result.clear();
result.add(Arrays.asList(xValue, yValue));
bestDiff = diff;
}
xIndex++;
}
}
return result;
}
Upvotes: 0