Given an array A
of N
integers we draw N
discs in a 2D plane, such that i-th disc has center in (0,i)
and a radius
Here's a O(N) time, O(N) space algorithm requiring 3 runs across the array and no sorting, verified scoring 100%:
You're interested in pairs of discs. Each pair involves one side of one disc and the other side of the other disc. Therefore we won't have duplicate pairs if we handle one side of each disc. Let's call the sides right and left (I rotated the space while thinking about it).
An overlap is either due to a right side overlapping another disc directly at the center (so pairs equal to the radius with some care about the array length) or due to the number of left sides existing at the rightmost edge.
So we create an array that contains the number of left sides at each point and then it's a simple sum.
C code:
int solution(int A[], int N) {
int C[N];
int a, S=0, t=0;
// Mark left and middle of disks
for (int i=0; i<N; i++) {
C[i] = -1;
a = A[i];
if (a>=i) {
C[0]++;
} else {
C[i-a]++;
}
}
// Sum of left side of disks at location
for (int i=0; i<N; i++) {
t += C[i];
C[i] = t;
}
// Count pairs, right side only:
// 1. overlaps based on disk size
// 2. overlaps based on disks but not centers
for (int i=0; i<N; i++) {
a = A[i];
S += ((a<N-i) ? a: N-i-1);
if (i != N-1) {
S += C[((a<N-i) ? i+a: N-1)];
}
if (S>10000000) return -1;
}
return S;
}
There are a lot of great answers here already, including the great explanation from the accepted answer. However, I wanted to point out a small observation about implementation details in the Python language.
Originally, I've came up with the solution shown below. I was expecting to get O(N*log(N))
time complexity as soon as we have a single for-loop with N
iterations, and each iteration performs a binary search that takes at most log(N)
.
def solution(a):
import bisect
if len(a) <= 1:
return 0
cuts = [(c - r, c + r) for c, r in enumerate(a)]
cuts.sort(key=lambda pair: pair[0])
lefts, rights = zip(*cuts)
n = len(cuts)
total = 0
for i in range(n):
r = rights[i]
pos = bisect.bisect_right(lefts[i+1:], r)
total += pos
if total > 10e6:
return -1
return total
However, I've get O(N**2)
and a timeout failure. Do you see what is wrong here? Right, this line:
pos = bisect.bisect_right(lefts[i+1:], r)
In this line, you are actually taking a copy of the original list to pass it into binary search function, and it totally ruins the efficiency of the proposed solution! It makes your code just a bit more consice (i.e., you don't need to write pos - i - 1
) but heavily undermies the performance. So, as it was shown above, the solution should be:
def solution(a):
import bisect
if len(a) <= 1:
return 0
cuts = [(c - r, c + r) for c, r in enumerate(a)]
cuts.sort(key=lambda pair: pair[0])
lefts, rights = zip(*cuts)
n = len(cuts)
total = 0
for i in range(n):
r = rights[i]
pos = bisect.bisect_right(lefts, r)
total += (pos - i - 1)
if total > 10e6:
return -1
return total
It seems that sometimes one could be too eager about making slices and copies because Python allows you to do it so easily :) Probably not a great insight, but for me it was a good lesson to pay more attention to these "technical" moments when converting ideas and algorithms into real-word solutions.
Well, I adapted Falk Hüffner's idea to c++, and made a change in the range. Opposite to what is written above, there is no need to go beyond the scope of the array (no matter how large are the values in it). On Codility this code received 100%. Thank you Falk for your great idea!
int number_of_disc_intersections ( const vector<int> &A ) {
int sum=0;
vector<int> start(A.size(),0);
vector<int> end(A.size(),0);
for (unsigned int i=0;i<A.size();i++){
if ((int)i<A[i]) start[0]++;
else start[i-A[i]]++;
if (i+A[i]>=A.size()) end[A.size()-1]++;
else end[i+A[i]]++;
}
int active=0;
for (unsigned int i=0;i<A.size();i++){
sum+=active*start[i]+(start[i]*(start[i]-1))/2;
if (sum>10000000) return -1;
active+=start[i]-end[i];
}
return sum;
}
An Implementation of Idea stated above in Java:
public class DiscIntersectionCount {
public int number_of_disc_intersections(int[] A) {
int[] leftPoints = new int[A.length];
for (int i = 0; i < A.length; i++) {
leftPoints[i] = i - A[i];
}
Arrays.sort(leftPoints);
// System.out.println(Arrays.toString(leftPoints));
int count = 0;
for (int i = 0; i < A.length - 1; i++) {
int rpoint = A[i] + i;
int rrank = getRank(leftPoints, rpoint);
//if disk has sifnificant radius, exclude own self
if (rpoint > i) rrank -= 1;
int rank = rrank;
// System.out.println(rpoint+" : "+rank);
rank -= i;
count += rank;
}
return count;
}
public int getRank(int A[], int num) {
if (A==null || A.length == 0) return -1;
int mid = A.length/2;
while ((mid >= 0) && (mid < A.length)) {
if (A[mid] == num) return mid;
if ((mid == 0) && (A[mid] > num)) return -1;
if ((mid == (A.length - 1)) && (A[mid] < num)) return A.length;
if (A[mid] < num && A[mid + 1] >= num) return mid + 1;
if (A[mid] > num && A[mid - 1] <= num) return mid - 1;
if (A[mid] < num) mid = (mid + A.length)/2;
else mid = (mid)/2;
}
return -1;
}
public static void main(String[] args) {
DiscIntersectionCount d = new DiscIntersectionCount();
int[] A =
//{1,5,2,1,4,0}
//{0,0,0,0,0,0}
// {1,1,2}
{3}
;
int count = d.number_of_disc_intersections(A);
System.out.println(count);
}
}
Here my JavaScript solution, based in other solutions in this thread but implemented in other languages.
function solution(A) {
let circleEndpoints = [];
for(const [index, num] of Object.entries(A)) {
circleEndpoints.push([parseInt(index)-num, true]);
circleEndpoints.push([parseInt(index)+num, false]);
}
circleEndpoints = circleEndpoints.sort(([a, openA], [b, openB]) => {
if(a == b) return openA ? -1 : 1;
return a - b;
});
let openCircles = 0;
let intersections = 0;
for(const [endpoint, opening] of circleEndpoints) {
if(opening) {
intersections += openCircles;
openCircles ++;
} else {
openCircles --;
}
if(intersections > 10000000) return -1;
}
return intersections;
}
This got 100/100 in c#
class CodilityDemo3
{
public static int GetIntersections(int[] A)
{
if (A == null)
{
return 0;
}
int size = A.Length;
if (size <= 1)
{
return 0;
}
List<Line> lines = new List<Line>();
for (int i = 0; i < size; i++)
{
if (A[i] >= 0)
{
lines.Add(new Line(i - A[i], i + A[i]));
}
}
lines.Sort(Line.CompareLines);
size = lines.Count;
int intersects = 0;
for (int i = 0; i < size; i++)
{
Line ln1 = lines[i];
for (int j = i + 1; j < size; j++)
{
Line ln2 = lines[j];
if (ln2.YStart <= ln1.YEnd)
{
intersects += 1;
if (intersects > 10000000)
{
return -1;
}
}
else
{
break;
}
}
}
return intersects;
}
}
public class Line
{
public Line(double ystart, double yend)
{
YStart = ystart;
YEnd = yend;
}
public double YStart { get; set; }
public double YEnd { get; set; }
public static int CompareLines(Line line1, Line line2)
{
return (line1.YStart.CompareTo(line2.YStart));
}
}
}