使用TreeSet计算磁盘交叉点

时间:2012-12-26 15:14:07

标签: java

在我将真实测试作为工作申请的一部分之前,我正在尝试Codility的演示问题。他们的演示之一是涉及计算磁盘阵列的磁盘交叉点数量的问题。

任务说明

  

给定N个整数的数组A,我们在2D平面中绘制N个圆盘   第I个光盘以(0,I)为中心,半径为A [I]。我们   如果J≠K且J-th和J,则说第J个圆盘和第K个圆盘相交   第K个光盘至少有一个共同点。写一个函数:class   解决方案{public int number_of_disc_intersections(int [] A);那个,   给出一个描述如上所述的N个盘的阵列A,返回   相交光盘对的数量。

您可以查看测试here

有一些明显的O(n ^ 2)时间复杂度解决方案,但目标是O(n * log(n))。

我想出了这个,它适用于我提供的任何例子,以及由codility([1,5,2,1,4,0​​])给出的简单测试用例,但Codility告诉我它大多数人都失败了,但我不明白为什么。

它应该是O(n log n),因为将n个磁盘中的每一个添加到TreeSet是log n,然后我们遍历每个磁盘,只有O(1)操作TreeSet.headSet()。

import java.util.*;

class Circle implements Comparable<Circle> {
  long edge;
  int index;

  Circle (long e, int i){
    edge = e;
    index = i;
  }

  long getRightAssumingEdgeIsLeft(){
    return (long)(2*index - edge + 1);
  }

  @Override
  public int compareTo(Circle other){
    return Long.valueOf(edge).compareTo(other.edge);
  }
}

class Solution {
  public int number_of_disc_intersections ( int[] A ) {
    int N = A.length;
    if (N<2) return 0;
    int result = 0;

    SortedSet<Circle> leftEdges  = new TreeSet<Circle>();
    for (int i=0; i<N; i++) {
      leftEdges.add( new Circle( (long)(i-A[i]), i ) );
    }
    int counter = 0;
    for (Circle c : leftEdges) {
      long rightEdge = c.getRightAssumingEdgeIsLeft();
      Circle dummyCircle = new Circle (rightEdge, -1);
      SortedSet<Circle> head = leftEdges.headSet(dummyCircle);
      result +=  head.size() - counter;
      if (result > 10000000) return -1;
      counter++;
    }
    return result;
  }
}

8 个答案:

答案 0 :(得分:18)

另一种算法(O(N log N)):

这个场景的错误绘图:

enter image description here

可以翻译成范围列表:(不完全相同的情况)

图。 2 enter image description here

O(N log N):我们首先对标记进行排序,如果我们想将切线光盘计算为重叠,请注意绿色标记出现在红色标记之前。

O(N):我们从左向右扫描,total最初为= 0overlaps最初为= 0。每当我们点击绿色标记total += 1时,以及每个红色标记total -= 1。此外,在每个绿色标记处if total > 0, then overlaps += total

图2中的黑色数字在每一步都是total;橙色是overlaps

然后overlaps应该是答案。

请参阅此处的粗略实施:http://ideone.com/ggiRPA

答案 1 :(得分:2)

有一种更简单的方法......

  1. 创建2个N元素数组(leftEdge,rightEdge)。
  2. 对于每个元素计算左右边缘(索引 - / +值)并将其设置为数组。
  3. 排序数组。
  4. 对于rightEdge数组中的每个元素,通过leftEdge数组循环查找第一个更大或相等的元素。保存剩余元素数和当前索引。对于保存索引的下一个元素启动循环...
  5. 这样我们实际上只遍历每个排序的数组一次,因此算法的复杂度为O(N log N)。

答案 2 :(得分:2)

此方法不需要任何特殊类,如圆圈或复杂容器,如PriorityQueue或TreeSet。只需要简单的整数数组。它是O(N * logN)。语言是Java。

public int numberOfDiscIntersections(int [] A) {
    // 0 <= A.length <= 100,000
    // 0 <= A[i] <= 2147483647
    int [] leftEdge = new int[A.length];
    int [] rightEdge = new int[A.length];

    int maxLength = 100000;
    // maxLength is used to prevent integers > 2147483647
    // and integers < -2147483647
    for (int i = 0; i < A.length; i++) {
        leftEdge[i] = i - A[i];
        rightEdge[i] = i - maxLength + A[i];
    }
    Arrays.sort(leftEdge);
    Arrays.sort(rightEdge);

    int sum = mergeAndCountOverlaps(leftEdge,rightEdge, maxLength);
    return sum;
}

合并例程是合并排序的修改合并。它合并了两个排序 数组,保持排序顺序不变并添加重叠计数功能。在这种情况下,我们不需要返回合并数组,只需要重叠计数。

private int mergeAndCountOverlaps(int[] leftEdge, int [] rightEdge, int maxLength) {
    int leftIndex = 0;
    int rightIndex = 0;
    int sum = 0;
    int total = 0;
    while ((leftIndex < leftEdge.length) || (rightIndex < rightEdge.length)) {
        if ((leftIndex < leftEdge.length) && (rightIndex < rightEdge.length)) {
            boolean compareLeftEdgeandRightEdge;
            if (leftEdge[leftIndex] < -2147483647 + maxLength) {
                compareLeftEdgeandRightEdge = leftEdge[leftIndex] <= rightEdge[rightIndex] + maxLength;
            } else {
                compareLeftEdgeandRightEdge = leftEdge[leftIndex] - maxLength <= rightEdge[rightIndex];
            }
            if (compareLeftEdgeandRightEdge) {
                // a new left edge
                sum += total;
                if (sum > 10000000) {
                    return -1;
                }
                total++;
                leftIndex++;
            } else {
                // a new right edge
                total--;
                rightIndex++;
            }
        } else if (leftIndex < leftEdge.length) {
            // a new left edge
            sum += total;
            if (sum > 10000000) {
                return -1;
            }
            total++;
            leftIndex++;
        } else if (rightIndex < rightEdge.length) {
            // a new right edge
            total--;
            rightIndex++;
        }
    }
    return sum;
}

答案 3 :(得分:0)

首先:你定义了compareTo()但不是equals()。 TreeSet JavaDoc说:“由集合维护的排序(无论是否提供显式比较器)必须与equals

一致

其他奇怪之处:我不明白edge字段是什么,也不知道为什么要将其设置为i - A[i]

答案 4 :(得分:0)

我为编程位置做了相同的演示。我没有得到及时开发的解决方案,结果得分很糟糕(十几岁的时候)。然而,对这个问题很感兴趣,我继续自己完成了这个问题。这是我的解决方案:

 ============================================================================
 Name        : cFundementalsTest.c
 Copyright   : Your copyright notice
 Description : Hello World in C, Ansi-style
 ============================================================================
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

int main(void) {

    int N = 5;
    int A[6] = {1, 5, 2, 1, 4, 0 };

        int pos_1, pos_2;
        int total;
        for(pos_1=0;pos_1<=N;pos_1++)
        {
            for(pos_2=pos_1+1;pos_2<=N;pos_2++)
            {
                if(A[pos_1] + A[pos_2] >= abs(pos_1 - pos_2))
                { // they share a common point
                    total++;
                    printf("%d and %d\n",pos_1, pos_2);
                    if(total > 10000000)
                        return(-1);
                }
            }
        }
        printf ("\n\n the total is %d",total);
}

以下是看起来正确的结果:

0 and 1
0 and 2
0 and 4
1 and 2
1 and 3
1 and 4
1 and 5
2 and 3
2 and 4
3 and 4
4 and 5

 the total is 11

答案 5 :(得分:0)

Codility上,您需要使用排序来解决它,对吗?以下是JavaScript解决方案通过100%测试

我们在轴x上使用两个范围的数组。两个数组中的值相同,但首先按from参数排序,然后按to排序(均升序)。然后,我们同时扫描两个阵列并跟踪重叠以计算不同的对。

您可以在jsbin中使用此代码。

function solution(arr) {
  const r1 = arr.map((n, i) => ({from: i - n, to: n + i})).sort((a, b) => a.from - b.from)
  const r2 = r1.slice().sort((a, b) => a.to - b.to)
  let i = 0, j = 0
  let overlaps = 0
  let pairs = 0

  while (i < r1.length || j < r2.length) {
    if (i < r1.length && r1[i].from <= r2[j].to) {
      pairs += overlaps
      if (pairs > 10000000) {
        return -1
      }
      overlaps++
      i++
    } else {
      overlaps--
      j++
    }
  }
  return pairs
}

答案 6 :(得分:0)

class Solution {
    public int solution(int[] A) {
        // write your code in Java SE 8
        long[] leftpoint=new long[A.length];
        long[] rigthpoint=new long[A.length];
        if(A.length<2)return 0;
    
        for(int i=0;i<A.length;++i){
            leftpoint[i]=-A[i];
            leftpoint[i]+=i;
            rigthpoint[i]=A[i];
            rigthpoint[i]+=i;
        }
        Arrays.sort(leftpoint);
        Arrays.sort(rigthpoint);
        int intersection =0;
        int j=0;
        int i=0;
        int open=0;
        while(i<A.length&&j<A.length){
             if(rigthpoint[i]>=leftpoint[j]){
                   intersection+=open; 
                   open++; 
                   j++;
             }else{
                 open--;
                 i++;
             }
             if(intersection>10000000)return -1;
            }
        return intersection;
    }
}

1)创建两个数组,一个用于圆的左边点,一个用于右边 2)对数组进行排序 3)创建一个变量以保持您通过的左侧点数少于您通过的右侧点数(打开)和一个到 ans(交叉点)和拖曳以在阵列上运行 4)我们都有未关闭的开放点!对于任何小于左点的右点我们有许多额外的交点作为尚未闭合的点数,因此我们将它们添加到答案中 + (open++;"new open point 因为我们已经到达了新的左点") 如果我们到达一个关闭的点,我们从开放的圆圈中删除一个,因为一个圆圈将不再位于另一个交叉路口 算法的思想是注意每个左边的点都与只比它小的左边点相交,所以我们只能从左边算出比我们多左边的点有多少!重要的是要理解,当有一个点封闭一个圆时,其中一个圆不再参与“开--”,因此如果左点小于正在检查的点

答案 7 :(得分:-2)

假设j总是大于i,为了满足两个圆的相互作用,下面的不等式应该总是有效:

|R(i) - R(j)| <= j - i <= R(i) + R(j)

这是另一种说法:

abs(A[i] - A[j]) <= j - i <= A[i] + A[j]

我没有测试过,但我认为它有效。 希望它有所帮助。

#include <stdlib.h>

public int number_of_disc_intersections(int[] A){

    int len = A.length;
    int intersections = 0;

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

        if(A[i] <= 0){
            continue;
        }

        for(int j = i + 1; j < len; j++){

            if(A[j] <= 0){
                continue;       
            }

            if(abs((A[i] - A[j]) <= j - i) && (j - i <= A[i] + A[j])){
                intersections ++;
            }
        }
    }

    return intersections;
}