views:

2032

answers:

5

Given an array of integers, A1, A2, ..., An, including negatives and positives, and another integer S. Now we need to find three different integers in the array, whose sum is closest to the given integer S. If there exists more than one solution, any of them is ok.

You can assume all the integers are within int32_t range, and no arithmetic overflow will occur with calculating the sum. S is nothing special but a randomly picked number.

Is there any efficient algorithm other than brute force search to find the three integers?

A: 

this is the subset sum problem with an additional constraint on the size of the subset (which at least reduces the number of subsets you need to check!)

jk
Not quite. Subset-sum is NP-complete, but this problem can be solved in O(n^2) with a simple algorithm. It's possible to do even better if you're okay with representing integers as bit vectors.
John Feminella
yes i missesd the fixed subset size on first read
jk
A subset sum problem with the constraint of 3 integers can be solved in O(N^2) as well.
Anurag
Actually I can't think of a better solution than O(N^2logN) for this problem, but shirley there exists one :)
Anurag
given the integers are bounded the dynamic approach
jk
+1  A: 

Very simple N^2*logN solution: sort the input array, then go through all pairs Ai, Aj (N^2 time), and for each pair check whether (S - Ai - Aj) is in array (logN time).

Another O(S*N) solution uses classical dynamic programming approach.

In short:

Create an 2-d array V[4][S + 1]. Fill it in such a way, that:

V[0][0] = 1, V[0][x] = 0;

V1[Ai]= 1 for any i, V1[x] = 0 for all other x

V[2][Ai + Aj]= 1, for any i, j. V[2][x] = 0 for all other x

V[3][sum of any 3 elements] = 1.

To fill it, iterate through Ai, for each Ai iterate through the array from right to left.

Olexiy
slight change to the first algorithm.. if the element doesn't exist, then at the end of the binary search, we'd have to look at the element on the left, current, and right to see which one gives the closest result.
Anurag
+11  A: 

Is there any efficient algorithm other than brute force search to find the three integers?

Yep; we can solve this in O(n2) time! First, consider that your problem P can be phrased equivalently in a slightly different way that eliminates the need for a "target value":

original problem P: Given an array A of n integers and a target value S, does there exist a 3-tuple from A that sums to S?

modified problem P': Given an array A of n integers, does there exist a 3-tuple from A that sums to zero?

Notice that you can go from this version of the problem P' from P by subtracting your target value from each element in A, but now you don't need the target value anymore.

Clearly, if we simply test all possible 3-tuples, we'd solve the problem in O(n3) -- that's the brute-force baseline. Is it possible to do better? What if we pick the tuples in a somewhat smarter way?

First, we invest some time to sort the array, which costs us an initial penalty of O(n log n). Now we execute this algorithm:

for (i in 1..n-2) {
  j = i  // Start where i is.
  k = n  // Start at the end of the array.

  while (k >= j) {
    // We got a match! All done.
    return (A[i], A[j], A[k]) if (A[i] + A[j] + A[k] == 0)

    // We didn't match. Let's try to get a little closer:
    //   If the sum was too big, decrement k.
    //   If the sum was too small, increment j.
    (A[i] + A[j] + A[k] > 0) ? k-- : j++
  }
  // When the while-loop finishes, j and k have passed each other and there's
  // no more useful combinations that we can try with this i.
}

This algorithm works by placing three pointers, i, j, and k at various points in the array. i starts off at the beginning and slowly works its way to the end. k points to the very last element. j points to where i has started at. We iteratively try to sum the elements at their respective indices, and each time one of the following happens:

  • The sum is exactly right! We've found the answer.
  • The sum was too small. Move j closer to the end to select the next biggest number.
  • The sum was too big. Move k closer to the beginning to select the next smallest number.

For each i, the pointers of j and k will gradually get closer to each other. Eventually they will pass each other, and at that point we don't need to try anything else for that i, since we'd be summing the same elements, just in a different order. After that point, we try the next i and repeat.

Eventually, we'll either exhaust the useful possibilities, or we'll find the solution. You can see that this is O(n2) since we execute the outer loop O(n) times and we execute the inner loop O(n) times. It's possible to do this sub-quadratically if you get really fancy, by representing each integer as a bit vector and performing a fast Fourier transform, but that's beyond the scope of this answer.


Note: Because this is an interview question, I've cheated a little bit here: this algorithm allows the selection of the same element multiple times. That is, (-1, -1, 2) would be a valid solution, as would (0, 0, 0). It also finds only the exact answers, not the closest answer, as the title mentions. As an exercise to the reader, I'll let you figure out how to make it work with distinct elements only (but it's a very simple change) and exact answers (which is also a simple change).

John Feminella
Thanks for fantastic, clear explanation.
Tim Drisdelle
It seems the algorithm can only find 3-tuple that *equals* to S, not *closest* to S.
ZelluX
ZelluX: As I mentioned in the note, I didn't want to give too much away since it's an interview problem. Hopefully you can see how to modify it so that it gets you the closest answer, though. (Hint: one way is to keep track of the closest answer so far and overwrite it if you find a better one.)
John Feminella
@John Feminella: I have thought about this algorithm before, but I thought it might miss some closer sum when moving the two pointer, and after trying to find some counterexamples I figure out that's a right algorithm :P
ZelluX
You claim that "Notice that you can go from this version of the problem P' from P by subtracting your target value from each element in A, but now you don't need the target value anymore." If you do this you'll end up with numbers y0 = x0-S, y1 = x1-S, y2 = x2-S such that y0+y1+y2 add to 0, then x0+x1+x2 = 3S not S... Now if S is divisible by 3 then you could subtract S/3 instead of S .. but what if it is not?
Michael Anderson
@Michael Anderson: Sorry, I wasn't completely clear about this part, and you're right; you must subtract S/3, not S. However, it's the same problem even if `S` isn't divisible by 3. For instance, say you want to find a 3-tuple for S = 11 in [1, 2, 4, 5]. That's the same as finding which numbers sum to zero in [(1 - 11/3), (2 - 11/3), (4 - 11/3), (5 - 11/3)]. The algorithm only cares that numbers can be summed; it doesn't actually care that they're integers.
John Feminella
@John Feminella: That makes sense. Anyone implementing this will just need to be a bit careful about data type conversions.
Michael Anderson
+1  A: 

How about something like this, which is O(n^2)

for(each ele in the sorted array)
{
    ele = arr[i] - YOUR_NUMBER;
    let front be the pointer to the front of the array;
    let rear be the pointer to the rear element of the array.;

    // till front is not greater than rear.                    
    while(front <= rear)
    {
        if(*front + *rear == ele)
        {
            print "Found triplet "<<*front<<","<<*rear<<","<<ele<<endl;
            break;
        }
        else
        {
            // sum is > ele, so we need to decrease the sum by decrementing rear pointer.
            if((*front + *rear) > ele)
                decrement rear pointer.
            // sum is < ele, so we need to increase the sum by incrementing the front pointer.
            else
                increment front pointer.
        }
    }

This finds if sum of 3 elements is exactly equal to your number. If you want closest, you can modify it to remember the smallest delta(difference between your number of current triplet) and at the end print the triplet corresponding to smallest delta.

codaddict
A: 

Note that we have a sorted array. This solution is similar to John's solution only that it looks for the sum and does not repeat the same element

#include <stdio.h>


int checkForSum (int arr[], int len, int sum) { //arr is sorted


        int i;
        for (i = 0; i < len ; i++) {
                int left = i + 1;
                int right = len - 1;

                while (right > left) {

                        printf ("values are %d %d %d\n", arr[i], arr[left], arr[right]);
                        if (arr[right] + arr[left] + arr[i] - sum == 0) {
                                printf ("final values are %d %d %d\n", arr[i], arr[left], arr[right]);
                                return 1;
                        }
                        if (arr[right] + arr[left] + arr[i] - sum > 0)
                                right--;
                        else
                                left++;

                }

        }
        return -1;
}




int main (int argc, char **argv) {

        int arr[] = {-99, -45, -6, -5, 0, 9, 12, 16, 21, 29};
        int sum = 4;
        printf ("check for sum %d in arr is %d\n", sum, checkForSum(arr, 10, sum));


}
Pasada