views:

180

answers:

3

I have a fixed array of constant integer values about 300 items long (Set A). The goal of the algorithm is to pick two numbers (X and Y) from this array that fit several criteria based on input R.

Formal requirement:
Pick values X and Y from set A such that the expression X*Y/(X+Y) is as close as possible to R.

That's all there is to it. I need a simple algorithm that will do that.

Additional info:
The Set A can be ordered or stored in any way, it will be hard coded eventually. Also, with a little bit of math, it can be shown that the best Y for a given X is the closest value in Set A to the expression X*R/(X-R). Also, X and Y will always be greater than R

From this, I get a simple iterative algorithm that works ok:

int minX = 100000000;
int minY = 100000000;
foreach X in A
    if(X<=R)
        continue;
    else
        Y=X*R/(X-R)
        Y=FindNearestIn(A, Y);//do search to find closest useable Y value in A
        if( X*Y/(X+Y) < minX*minY/(minX+minY) )
        then
            minX = X;
            minY = Y;
        end
    end
end

I'm looking for a slightly more elegant approach than this brute force method. Suggestions?

+7  A: 

For a possibly 'more elegant' solution see Solution 2.


Solution 1)

Why don't you create all the possible 300*300/2 or (300*299/2) possible exact values of R, sort them into an array B say, and then given an R, find the closest value to R in B using binary search and then pick the corresponding X and Y.

I presume that having array B (with the X&Y info) won't be a big memory hog and can easily be hardcoded (using code to write code! :-)).

This will be reasonably fast: worst case ~ 17 comparisons.


Solution 2)

You can possibly also do the following (didn't try proving it, but seems correct):

Maintain an array of the 1/X values, sorted.

Now given an R, you try and find the closest sum to 1/R with two numbers in the array of 1/Xs.

For this you maintain two pointers to the 1/X array, one at the smallest and one at the largest, and keep incrementing one and decrementing the other to find the one closest to 1/R. (This is a classic interview question: Find if a sorted array has two numbers which sum to X)

This will be O(n) comparisons and additions in the worst case. This is also prone to precision issues. You could avoid some of the precision issues by maintaining a reverse sorted array of X's, though.

Moron
This would take roughly 2MB of memory on most systems I believe.
CodeFusionMobile
@CSharperWithJava: Is that not acceptable? You can always put it in a disk backed B-Tree! ;-)
Moron
But a single disk access would probably be slower than the original O(nlogn) algorithm! :)
Dimitris Andreou
@Dimitris: Perhaps, but do a million of those, and with OS caching pages/good page replacement policy, the disk based one might still beat the other one. Of course, if using 2MB memory itself is a problem, I suspect a disk might not be available too (perhaps some embedded device).
Moron
Yes, RAM is the new disk they say :)
Dimitris Andreou
@Moron The entire executable for the OP algorithm including VB Form is 36KB. This solution works and would be faster, but it isn't quite the elegant solution I was hoping for.
CodeFusionMobile
@CSharperWithJava: I have edited the answer to add another method which might appeal to you.
Moron
+1 The second solution is beautiful!
Eyal Schneider
+1, I didn't spot that `X*Y/(X+Y) == R` meant `1/X + 1/Y == 1/R`!
Matthieu M.
A: 

When the size of the input is (roughly) constant, an O(n*log(n)) solution might run faster than a particular O(n) solution.

I would start with the solution that you understand the best, and optimize from there if needed.

mbeckish
By the way, if n is a constant, O(n*logn) is O(1) :)
Dimitris Andreou
Yes, but if I am using this algorithm within another loop to calculate many values of X and Y for many Rs, O(1) might be too long.
CodeFusionMobile
@Dimitris - That is my point. Since the input size is fixed, all algorithms are constant time, so you need to focus on which has the smaller constant, not which has the better run-time as a function of n.
mbeckish
+1  A: 

Two ideas come to my mind:

1) Since the set A is constant, some pre-processing can be helpful. Assuming the value span of A is not too large, you can create an array of size N = max(A). For each index i you can store the closest value in A to i. This way you can improve your algorithm by finding the closest value in constant time, instead of using a binary search.

2) I see that you omit X<=R, and this is correct. If you define that X<=Y, you can restrict the search range even further, since X>2R will yield no solutions either. So the range to be scanned is R<X<=2R, and this guarantees no symetric solutions, and that X<=Y.

Eyal Schneider
Good point about the range restriction. That's very helpful.
CodeFusionMobile