4

Given an array with x elements, I must find four numbers that, when summed, equal zero. I also need to determine how many such sums exist.

So the cubic time involves three nested iterators, so we just have to look up the last number (with binary search).

Instead by using the cartesian product (same array for X and Y) we can store all pairs and their sum in a secondary array. So for each sum d we just have to look for -d.

This should look something like for (close to) quadratic time:

public static int quad(Double[] S) {
  ArrayList<Double> pairs = new ArrayList<>(S.length * S.length);
  int count = 0;

  for (Double d : S) {
    for (Double di : S) {
      pairs.add(d + di);
    }
  }

  Collections.sort(pairs);

  for (Double d : pairs) {
    int index = Collections.binarySearch(pairs, -d);
    if (index > 0) count++; // -d was found so increment
  }

  return count;
}

With x being 353 (for our specific array input), the solution should be 528 but instead I only find 257 using this solution. For our cubic time we are able to find all 528 4-sums

public static int count(Double[] a) {
  Arrays.sort(a);
  int N = a.length;
  int count = 0;

  for(int i = 0; i < N; i++) {
    for (int j = 0; j < N; j++) {
      for (int k = 0; k < N; k++) {
        int l = Arrays.binarySearch(a, -(a[i] + a[j] + a[k]));
        if (l > 0) count++;
      }
    }
  }
  return count;
}

Is the precision of double lost by any chance?

EDIT: Using BigDecimal instead of double was discussed, but we were afraid it would have an impact on performance. We are only dealing with 353 elements in our array, so would this mean anything to us?

EDITEDIT: I apologize if I use BigDecimal incorrectly. I have never dealt with the library before. So after multiple suggestions I tried using BigDecimal instead

public static int quad(Double[] S) {
  ArrayList<BigDecimal> pairs = new ArrayList<>(S.length * S.length);
  int count = 0;

  for (Double d : S) {
    for (Double di : S) {
      pairs.add(new BigDecimal(d + di));
    }
  }

  Collections.sort(pairs);

  for (BigDecimal d : pairs) {
    int index = Collections.binarySearch(pairs, d.negate());
    if (index >= 0) count++;
  }

  return count;
}

So instead of 257 it was able to find 261 solutions. This might indicate there is a problem double and I am in fact losing precision. However 261 is far away from 528, but I am unable to locate the cause.

LASTEDIT: So I believe this is horrible and ugly code, but it seems to be working none the less. We had already experimented with while but with BigDecimal we are now able to get all 528 matches.

I am not sure if it's close enough to quadratic time or not, time will tell.
I present you the monster:

public static int quad(Double[] S) {
  ArrayList<BigDecimal> pairs = new ArrayList<>(S.length * S.length);
  int count = 0;

   for (Double d : S) {
    for (Double di : S) {
      pairs.add(new BigDecimal(d + di));
    }
  }

  Collections.sort(pairs);

  for (BigDecimal d : pairs) {
    BigDecimal negation = d.negate();
    int index = Collections.binarySearch(pairs, negation);
    while (index >= 0 && negation.equals(pairs.get(index))) {
      index--;
    }

    index++;

    while (index >= 0 && negation.equals(pairs.get(index))) {
      count++;
      index++;
    }
  }

  return count;
}
Filuren
  • 631
  • 2
  • 7
  • 19
  • This is not quadric. It is `O(n^2logn)`, `pairs` is of size `n^2` - and you sort it in `O(n^2*log(n^2)) = O(n^2logn)` – amit Feb 27 '14 at 20:01
  • Edited the post. It's just supposed to be close to quadratic, so `O(n^2 log(n))` should be fine as well. – Filuren Feb 27 '14 at 20:11
  • 1
    "Is the precision of double lost by any chance?" - If you're ever wondering, assume "Yes" (just ... as a rule). – Bernhard Barker Feb 27 '14 at 20:24
  • It is always a very bad idea to compare doubles for equality. – Brian English Feb 27 '14 at 20:28
  • what should be a result of an an input array: [1,-2,3]? – Timofey Feb 27 '14 at 20:37
  • 1
    `if (index > 0)` or `if (index >= 0)` ? – Yves Daoust Feb 27 '14 at 20:42
  • @Tim, we are allowed to assume input is always good. @Yves, `>` or `>=` had no influence, still returns 257 – Filuren Feb 27 '14 at 20:51
  • My question was more about considering same number multiple times. All loops in the second example start with 0. Do you think it is ok? – Timofey Feb 27 '14 at 20:58
  • Even though BigDecimal lowers performance, it should not be a performance concern here. Where I work, we've used it in complex batch processes that do millions+ of currency computations with no discernible performance impact from the BigDecimal class, ie we hit our target performance times. – Brian English Feb 27 '14 at 21:01
  • @Tim, sorry I misunderstood. It's okay to use the same number multiple times. The correct answer is 528, so our cubic is fine. I did a manual check of your input [1, -2, 3] and I expected 6, got 6 (with cubic), got 5 (with quadratic). – Filuren Feb 27 '14 at 21:17

2 Answers2

1

You should use the BigDecimal class instead of double here, since exact precision of the floating point numbers in your array adding up to 0 is a must for your solution. If one of your decimal values was .1, you're in trouble. That binary fraction cannot be precisely represented with a double. Take the following code as an example:

double counter = 0.0;
while (counter != 1.0)
{
    System.out.println("Counter = " + counter);
    counter = counter + 0.1;
}

You would expect this to execute 10 times, but it is an infinite loop since counter will never be precisely 1.0.

Example output:

Counter = 0.0
Counter = 0.1
Counter = 0.2
Counter = 0.30000000000000004
Counter = 0.4
Counter = 0.5
Counter = 0.6
Counter = 0.7
Counter = 0.7999999999999999
Counter = 0.8999999999999999
Counter = 0.9999999999999999
Counter = 1.0999999999999999
Counter = 1.2
Counter = 1.3
Counter = 1.4000000000000001
Counter = 1.5000000000000002
Counter = 1.6000000000000003
Brian English
  • 466
  • 2
  • 8
  • I updated my post to use BigDecimal. Did improve by 4 (from 257 to 261) so it could be a precision loss problem. BD did not fix it though unfortunately – Filuren Feb 27 '14 at 21:18
1

When you search for either pairs or an individual element, you need to count with multiplicity. I.e., if you find element -d in your array of either singletons or pairs, then you need to increase the count by the number of matches that are found, not just increase by 1. This is probably why you're not getting the full number of results when you search over pairs. And it could mean that the number 528 of matches is not the true full number when you are searching over singletons. And in general, you should not use double precision arithmetic for exact arithmetic; use an arbitrary precision rational number package instead.

user2566092
  • 4,473
  • 12
  • 19