'NumberOfDiscIntersections overflow in codility test

In the codility test NumberOfDiscIntersections I am getting perf 100% and correctness 87% with the one test failing being

overflow
arithmetic overflow tests
got -1 expected 2

I can't see what is causing that given that I am using long which is 64-bit. And even if I can get it to 100% perf 100% correctness I am wondering if there is a better way to do this that is not as verbose in Java.

edit: figured out a much better way to do with with two arrays rather than a pair class

// you can also use imports, for example:
 import java.util.*;

// you can use System.out.println for debugging purposes, e.g.
// System.out.println("this is a debug message");

class Solution {
    public int solution(int[] A) {
        int j = 0;
        Pair[] arr = new Pair[A.length * 2];
        for (int i = 0; i < A.length; i++) {
            Pair s = new Pair(i - A[i], true);
            arr[j] = s;
            j++;
            Pair e = new Pair(i + A[i], false);
            arr[j] = e;
            j++;
        }
        Arrays.sort(arr, new Pair(0, true));

        long numIntersect = 0;
        long currentCount = 0;
        for (Pair p: arr) {
            if (p.start) {
                numIntersect += currentCount;
                if (numIntersect > 10000000) {
                    return -1;
                }
                currentCount++;
            } else {
                currentCount--;
            }
        }

        return (int) numIntersect;
    }

    static private class Pair implements Comparator<Pair> {
        private long x;
        private boolean start;
        public Pair(long x, boolean start) {
            this.x = x;
            this.start = start;
        }

        public int compare(Pair p1, Pair p2) {
            if (p1.x < p2.x) {
                return -1;
            } else if (p1.x > p2.x) {
                return 1;
            } else {
                if (p1.start && p2.start == false) {
                    return -1;
                } else if (p1.start == false && p2.start) {
                    return 1;
                } else {
                    return 0;
                }
            }
        }
    }
}


Solution 1:[1]

My todays solution. O(N) time complexity. Simple assumption that number of availble pairs in next point of the table is difference between total open circle to that moment (circle) and circles that have been processed before. Maybe it's to simple :)

  public int solution04(int[] A) { 
    final int N = A.length;
    final int M = N + 2;
    int[] left  = new int[M]; // values of nb of "left"  edges of the circles in that point
    int[] sleft = new int[M]; // prefix sum of left[]
    int il, ir;               // index of the "left" and of the "right" edge of the circle

    for (int i = 0; i < N; i++) { // counting left edges
      il = tl(i, A);
      left[il]++;
    }

    sleft[0] = left[0];
    for (int i = 1; i < M; i++) {// counting prefix sums for future use
      sleft[i]=sleft[i-1]+left[i];
    }
    int o, pairs, total_p = 0, total_used=0;
    for (int i = 0; i < N; i++) { // counting pairs
      ir = tr(i, A, M);
      o  = sleft[ir];                // nb of open till right edge
      pairs  = o -1 - total_used;
      total_used++;
      total_p += pairs;
    }
    if(total_p > 10000000){
      total_p = -1;
    }
    return total_p;
  }

  int tl(int i, int[] A){
    int tl = i - A[i]; // index of "begin" of the circle
      if (tl < 0) {
        tl = 0;
      } else {
        tl = i - A[i] + 1;
      }
    return tl;
  }
  int tr(int i, int[] A, int M){
    int tr;           // index of "end" of the circle
      if (Integer.MAX_VALUE - i < A[i] || i + A[i] >= M - 1) {
        tr = M - 1;
      } else {
        tr = i + A[i] + 1;
      }
      return tr;
  }

Solution 2:[2]

My take on this, O(n):

public int solution(int[] A) {

    int[] startPoints = new int[A.length];
    int[] endPoints = new int[A.length];
    int tempPoint;

    int currOpenCircles = 0;
    long pairs = 0;

    //sum of starting and end points - how many circles open and close at each index?
    for(int i = 0; i < A.length; i++){
        tempPoint = i - A[i];
        startPoints[tempPoint < 0 ? 0 : tempPoint]++;
        tempPoint = i + A[i];
        if(A[i] < A.length && tempPoint < A.length) //first prevents int overflow, second chooses correct point
            endPoints[tempPoint]++;
    }

    //find all pairs of new circles (combinations), then make pairs with exiting circles (multiplication)
    for(int i = 0; i < A.length; i++){

        if(startPoints[i] >= 2)
            pairs += (startPoints[i] * (startPoints[i] - 1)) / 2;

        pairs += currOpenCircles * startPoints[i];

        currOpenCircles += startPoints[i];
        currOpenCircles -= endPoints[i];

        if(pairs > 10000000) 
            return -1;
    }

    return (int) pairs;
}

Solution 3:[3]

The explanation to Helsing's solution part:

if(startPoints[i] >= 2) pairs += (startPoints[i] * (startPoints[i] - 1)) / 2; 

is based on mathematical combinations formula:

Cn,m = n! / ((n-m)!.m!

for pairs, m=2 then:

Cn,2 = n! / ((n-2)!.2

Equal to:

Cn,2 = n.(n-1).(n-2)! / ((n-2)!.2

By simplification:

Cn,2 = n.(n-1) / 2

Solution 4:[4]

Not a very good performance, but using streams.

List<Long> list = IntStream.range(0, A.length).mapToObj(i -> Arrays.asList((long) i - A[i], (long) i + A[i]))
            .sorted((o1, o2) -> {
                int f = o1.get(0).compareTo(o2.get(0));
                return f == 0 ? o1.get(1).compareTo(o2.get(1)) : f;
            })
            .collect(ArrayList<Long>::new,
                    (acc, val) -> {
                        if (acc.isEmpty()) {
                            acc.add(0l);
                            acc.add(val.get(1));
                        } else {
                            Iterator it = acc.iterator();
                            it.next();
                            while (it.hasNext()) {
                                long el = (long) it.next();
                                if (val.get(0) <= el) {
                                    long count = acc.get(0);
                                    acc.set(0, ++count);
                                } else {
                                    it.remove();
                                }
                            }
                            acc.add(val.get(1));
                        }
                    },
                    ArrayList::addAll);
    return (int) (list.isEmpty() ? 0 : list.get(0) > 10000000 ? -1 : list.get(0));

Solution 5:[5]

This one in Python passed all "Correctness tests" and failed all "Performance tests" due to O(n²), so I got 50% score. But it is very simple to understand. I just used the right radius (maximum) and checked if it was bigger or equal to the left radius (minimum) of the next circles. I also avoided to use sort and did not check twice the same circle. Later I will try to improve performance, but the problem here for me was the algorithm. I tried to find a very easy solution to help explain the concept. Maybe this will help someone.

def solution(A):
     n = len(A)
     cnt = 0

     for j in range(1,n):
         for i in range(n-j):
             if(i+A[i]>=i+j-A[i+j]):
                 cnt+=1

     if(cnt>1e7):
         return -1
     return cnt

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Zbyszek
Solution 2 Helsing
Solution 3 jizhihaoSAMA
Solution 4 Guillermo Abbona
Solution 5 Will