Algorithm to calculate number of intersecting discs

前端 未结 30 1371
鱼传尺愫
鱼传尺愫 2020-12-12 10:57

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

相关标签:
30条回答
  • 2020-12-12 11:49

    100% score in Codility.

    Here is an adaptation to C# of Толя solution:

        public int solution(int[] A)
        {
            long result = 0;
            Dictionary<long, int> dps = new Dictionary<long, int>();
            Dictionary<long, int> dpe = new Dictionary<long, int>();
    
            for (int i = 0; i < A.Length; i++)
            {
                Inc(dps, Math.Max(0, i - A[i]));
                Inc(dpe, Math.Min(A.Length - 1, i + A[i]));
            }
    
            long t = 0;
            for (int i = 0; i < A.Length; i++)
            {
                int value;
                if (dps.TryGetValue(i, out value))
                {
                    result += t * value;
                    result += value * (value - 1) / 2;
                    t += value;
                    if (result > 10000000)
                        return -1;
                }
                dpe.TryGetValue(i, out value);
                t -= value;
            }
    
            return (int)result;
        }
    
        private static void Inc(Dictionary<long, int> values, long index)
        {
            int value;
            values.TryGetValue(index, out value);
            values[index] = ++value;
        }
    
    0 讨论(0)
  • 2020-12-12 11:50

    100/100 c#

      class Solution
        {
            class Interval
            {
                public long Left;
                public long Right;
            }
    
            public int solution(int[] A)
            {
                if (A == null || A.Length < 1)
                {
                    return 0;
                }
                var itervals = new Interval[A.Length];
                for (int i = 0; i < A.Length; i++)
                {
                    // use long to avoid data overflow (eg. int.MaxValue + 1)
                    long radius = A[i];
                    itervals[i] = new Interval()
                    {
                        Left = i - radius,
                        Right = i + radius
                    };
                }
                itervals = itervals.OrderBy(i => i.Left).ToArray();
    
                int result = 0;
                for (int i = 0; i < itervals.Length; i++)
                {
                    var right = itervals[i].Right;
                    for (int j = i + 1; j < itervals.Length && itervals[j].Left <= right; j++)
                    {
                        result++;
                        if (result > 10000000)
                        {
                            return -1;
                        }
                    }
                }
                return result;
            }
        }
    
    0 讨论(0)
  • 2020-12-12 11:51

    Probably extremely fast. O(N). But you need to check it out. 100% on Codility. Main idea: 1. At any point of the table, there are number of circles "opened" till the right edge of the circle, lets say "o". 2. So there are (o-1-used) possible pairs for the circle in that point. "used" means circle that have been processed and pairs for them counted.

      public int solution(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;
      }
    
        private 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;
      }
    
    0 讨论(0)
  • 2020-12-12 11:51

    Below is an implementation of the accepted answer by @Aryabhatta in Kotlin so all the credit goes @Aryabhatta

    fun calculateDiscIntersections(A: Array<Int>): Int {
        val MAX_PAIRS_ALLOWED = 10_000_000L
        //calculate startX and endX for each disc
        //as y is always 0 so we don't care about it. We only need X
        val ranges = Array(A.size) { i ->
            calculateXRange(i, A[i])
        }
    
        //sort Xranges by the startX
        ranges.sortBy { range ->
            range.start
        }
    
        val starts = Array(ranges.size) {index ->
            ranges[index].start
        }
    
        var count = 0
        for (i in 0 until ranges.size) {
            val checkRange = ranges[i]
    
            //find the right most disc whose start is less than or equal to end of current disc
            val index = bisectRight(starts, checkRange.endInclusive, i)
    
            //the number of discs covered by this disc are:
            //count(the next disc/range ... to the last disc/range covered by given disc/range)
            //example: given disc index = 3, last covered (by given disc) disc index = 5
            //count = 5 - 3 = 2
            //because there are only 2 discs covered by given disc
            // (immediate next disc with index 4 and last covered disc at index 5)
            val intersections = (index - i)
    
            //because we are only considering discs intersecting/covered by a given disc to the right side
            //and ignore any discs that are intersecting on left (because previous discs have already counted those
            // when checking for their right intersects) so this calculation avoids any duplications
            count += intersections
    
            if (count > MAX_PAIRS_ALLOWED) {
                return -1
            }
        }
    
        return if (count > MAX_PAIRS_ALLOWED) {
            -1
        } else {
            count
        }
    }
    
    private fun calculateXRange(x: Int, r: Int): LongRange {
        val minX = x - r.toLong()
        val maxX = x + r.toLong()
    
        return LongRange(minX, maxX)
    }
    
    fun bisectRight(array: Array<Long>, key: Long, arrayStart: Int = 0): Int {
        var start = arrayStart
        var end = array.size - 1
        var bisect = start
    
        while (start <= end) {
            val mid = Math.ceil((start + end) / 2.0).toInt()
            val midValue = array[mid]
            val indexAfterMid = mid + 1
    
            if (key >= midValue) {
                bisect = mid
            }
    
            if (key >= midValue && (indexAfterMid > end || key < array[indexAfterMid])) {
                break
            } else if (key < midValue) {
                end = mid - 1
            } else {
                start = mid + 1
            }
        }
    
        return bisect
    }
    

    Codility Solution with 100% score.

    0 讨论(0)
  • 2020-12-12 11:52

    This can even be done in linear time. In fact, it becomes easier if you ignore the fact that there is exactly one interval centered at each point, and just treat it as a set of start- and endpoints of intervals. You can then just scan it from the left (Python code for simplicity):

    from collections import defaultdict
    
    a = [1, 5, 2, 1, 4, 0]
    
    start = defaultdict(int)
    stop = defaultdict(int)
    
    for i in range(len(a)):
        start[i - a[i]] += 1
        stop[i + a[i]] += 1
    
    active = 0
    intersections = 0
    for i in range(-len(a), len(a)):
        intersections += active * start[i] + (start[i] * (start[i] - 1)) / 2    
        active += start[i]
        active -= stop[i]
    
    print intersections
    
    0 讨论(0)
  • 2020-12-12 11:54

    A Python answer

    from bisect import bisect_right
    
    def number_of_disc_intersections(li):
        pairs = 0
        # treat as a series of intervals on the y axis at x=0
        intervals = sorted( [(i-li[i], i+li[i]) for i in range(len(li))] )
        # do this by creating a list of start points of each interval
        starts = [i[0] for i in intervals]
        for i in range(len(starts)):
            # find the index of the rightmost value less than or equal to the interval-end
            count = bisect_right(starts, intervals[i][1])
            # subtract current position to exclude previous matches, and subtract self
            count -= (i+1)
            pairs += count
            if pairs > 10000000:
                return -1
        return pairs
    
    0 讨论(0)
提交回复
热议问题