计算数组中共线的三元组数

时间:2011-08-20 11:07:13

标签: c++ algorithm

我被问到这个面试问题(C ++,algos)并且不知道如何解决它。

给定一个数组,说包含N个不同点的笛卡尔坐标的Arr [N]计算三元组的数量(Arr [P],Arr [Q],Arr [R]),使得P <1。 Q&lt; R&lt; N和点Arr [P],Arr [Q],Arr [R]共线(即位于同一条直线上)。

有什么想法吗?我可以使用什么算法?

6 个答案:

答案 0 :(得分:6)

以下内容可能未经过优化,但其复杂性是您的采访者所要求的。

首先为每两个点创建一个(a,b,c)值列表(N²复杂度) - &GT; (a,b,c)代表直线a * x + b * y + c = 0的笛卡尔方程 给定两个点及其坐标(xa,ya)和(xb,yb),计算(a,b,c)很简单。 你可以找到

的解决方案
ya=alpha*xa+beta  
yb=alpha*xb+beta

(if (xb-xa) != 0)
alpha = (yb-ya)/(xb-xa)
beta = ya - alpha*xa
a = alpha
b = -1
c = beta

xa = gamma*ya+delta
xb = gamma*yb+delta
(you get the point)

然后可以用更一般的形式重写可解的方程组

a*x+b*y+c = 0

然后对列表进行排序(N²log(N²)复杂度因此N²log(N)复杂度)。

迭代列表中的元素。如果两个连续元素相等,则对应点是共线的。 N²复杂性。

你可能想要添加最后一个操作来过滤重复的结果,但你应该没那么复杂。

编辑:我在编码时对算法进行了更新,使其更简单,更优化。在这里。

#include <map>
#include <set>
#include <vector>
#include <iostream>

struct StraightLine
{
    double a,b,c;
    StraightLine() : a(0.),b(0.),c(0.){}
    bool isValid() { return a!=0. || b!= 0.; }
    bool operator<(StraightLine const& other) const
    {
        if( a < other.a ) return true;
        if( a > other.a ) return false;
        if( b < other.b ) return true;
        if( b > other.b ) return false;
        if( c < other.c ) return true;
        return false;
    }
};

struct Point { 
    double x, y; 
    Point() : x(0.), y(0.){}
    Point(double p_x, double p_y) : x(p_x), y(p_y){}
};

StraightLine computeLine(Point const& p1, Point const& p2)
{
    StraightLine line;
    if( p2.x-p1.x != 0.)
    {
        line.b = -1;
        line.a = (p2.y - p1.y)/(p2.x - p1.x);
    }
    else if( p2.y - p1.y != 0. )
    {
        line.a = -1;
        line.b = (p2.x-p1.x)/(p2.y-p1.y);
    }
    line.c = - line.a * p1.x - line.b * p1.y;
    return line;
}

int main()
{
    std::vector<Point> points(9);
    for( int i = 0 ; i < 3 ; ++i )
    {
        for( int j = 0; j < 3 ; ++j )
        {
            points[i*3+j] = Point((double)i, (double)j);
        }
    }


    size_t nbPoints = points.size();
    typedef std::set<size_t> CollinearPoints;
    typedef std::map<StraightLine, CollinearPoints> Result;
    Result result;

    for( int i = 0 ; i < nbPoints ; ++i )
    {
        for( int j = i + 1 ; j < nbPoints ; ++j )
        {
            StraightLine line = computeLine(points[i], points[j]);
            if( line.isValid() )
            {
                result[line].insert(i);
                result[line].insert(j);
            }
        }
    }

    for( Result::iterator currentLine = result.begin() ; currentLine != result.end(); ++currentLine )
    {
        if( currentLine->second.size() <= 2 )
        {
            continue;
        }
        std::cout << "Line";
        for( CollinearPoints::iterator currentPoint = currentLine->second.begin() ; currentPoint != currentLine->second.end() ; ++currentPoint )
        {
            std::cout << " ( " << points[*currentPoint].x << ", " << points[*currentPoint].y << ")";
        }
        std::cout << std::endl;
    }
    return 0;
}

答案 1 :(得分:2)

如果它是2个维度点:如果(P,Q),则(3,P,Q,R)是共线的,(P,R)定义相同的斜率。

m = (p.x - q.x) / (p.y - q.y)  ; slope

不知怎的,你需要检查所有可能的组合并检查,一个有效的算法是技巧,因为第一个天真是N *(N-1)*(N-2)......

答案 2 :(得分:2)

而不是3个循环,威尔是O(n³),预先计算由两个点Arr[P], Arr[Q]给出的所有线的斜率。那是O(n²)。然后比较这些斜率。

您可以在计算期间或之后通过其斜率进一步对线进行排序,即O(n log n)。之后找到具有相同斜率的线是O(n)。

但是,如果您想知道哪些点是共线的,您可能需要通过实施数据结构为此付出代价。

我认为面试问题的关键不在于提供完美的算法,而是在一个想法中识别和讨论问题。

修改:

蛮力方法:

#include <iostream>
#include <vector>

struct Point { int x, y; };
bool collinear(Point P, Point Q, Point R)
{
  // TODO: have to look up for math ... see icCube's answer
  return false; 
}

int main()
{
  std::vector<Point> v;

  Point a;
  while (std::cin >> a.x >> a.y)
  {
    v.push_back(a);
  }

  int count = 0;
  for (int p = 0; p < v.size(); ++p)
  {
    for (int q = p+1; q < v.size(); ++q)
    {
      for (int r = q+1; r < v.size(); ++r)
      {
        if (collinear(v[p], v[q], v[r])) ++count;
      }
    }  
  }
  std::cout << count << '\n';
  return 0;
}

答案 3 :(得分:2)

对于共线三联体的计数,识别具有任意两个点的线,然后检查由任何其他两个点形成的新线是否可能重合或平行,并且在计算共线三联体时需要注意。< / p>

要解决:

  1. 首先,使用Map<Line, Set<Point2d>>收集一行中的所有点 正如问题本身所示。
  2. 对于三元组过滤掉那些至少有三个点的行
  3. 然后计算每个添加到全局结果的nC3。
  4.   

    以下问题的代码

        import java.util.*;
    
        public class CollinearTriplets {
    
            public static void main(String[] args) {
                Point2d A[] = new Point2d[8];
                A[0] = new Point2d(0, 0);
                A[1] = new Point2d(1, 1);
                A[2] = new Point2d(2, 2);
                A[3] = new Point2d(3, 3);
                A[4] = new Point2d(3, 2);
                A[5] = new Point2d(4, 2);
                A[6] = new Point2d(5, 1);
                A[7] = new Point2d(4, 4);
    
                System.out.println(countCollinear(A));
            }
    
    
            public static int factorial(int n) {
                int fact = 1;
                for (int i = 1; i <= n; i++) {
                    fact = fact * i;
                }
                return fact;
            }
    
            private static int combinations(int n, int r) {
                return factorial(n) / (factorial(n - r) * factorial(r));
            }
    
            private static long countCollinear(Point2d[] points) {
                Map<Line, Set<Point2d>> lineToPoints = new HashMap<>();
                long result = 0;
    
                for (int i = 0; i < points.length; i++) {
                    for (int j = i + 1; j < points.length; j++) {
                        double slope = 0d, xIntercept, yIntercept; // Default slope paralell to y-axis
                        if (points[i].x == points[j].x) {
                            slope = Double.MAX_VALUE; // Horizontal slope parallel to x-axis
                        } else if (points[i].y != points[j].y) {
                            xIntercept = points[j].x - points[i].x;
                            yIntercept = points[j].y - points[i].y;
                            slope = yIntercept / xIntercept;
                        }
                        Line currLine = new Line(points[i], slope);
                        if (Objects.isNull(lineToPoints.get(currLine))) {
                            lineToPoints.put(currLine, new HashSet<>());
                        }
                        lineToPoints.get(currLine).add(points[i]);
                        lineToPoints.get(currLine).add(points[j]);
                    }
    
                }
                for (Line line : lineToPoints.keySet()) {
                    int size = lineToPoints.get(line).size();
                    if (size >= 3) {
                        result = result + combinations(size, 3);
                    }
                }
                return result;
            }
    
            /**
             * Line which contains the starting point and slope so that you can identify exact line
             * equals method is overridden to check whether any new line is coinciding or parallel
             */
            static class Line {
                Point2d point;
                double slope;
    
                public Line(Point2d point, double slope) {
                    this.point = point;
                    this.slope = slope;
                }
    
                @Override
                public boolean equals(Object o) {
                    if (this == o) return true;
                    if (!(o instanceof Line)) return false;
                    Line line = (Line) o;
    
                    if (line.slope == this.slope)
                        return ((((double) (line.point.y - this.point.y)) / (line.point.x - this.point.x)) == this.slope);
                    return false;
                }
    
                @Override
                public int hashCode() {
                    return Objects.hash(slope);
                }
            }
    
            static class Point2d {
                int x;
                int y;
    
                public Point2d(int x, int y) {
                    this.x = x;
                    this.y = y;
                }
    
                @Override
                public boolean equals(Object o) {
                    if (this == o) return true;
                    if (!(o instanceof Point2d)) return false;
                    Point2d point2d = (Point2d) o;
                    return x == point2d.x &&
                            y == point2d.y;
                }
    
                @Override
                public int hashCode() {
                    return Objects.hash(x, y);
                }
            }
        }
    

    上述代码O(N^2)和空间复杂度的时间复杂度为O(N)

答案 4 :(得分:1)

看到你可以得到所有的点数和它们的斜率和它们是微不足道的。 y-截取O(n ^ 2)时间。所以输出是:

IndexB Slope Y-Intercept IndexA

当然,我们不会插入任何IndexA = IndexB的条目。

让我们将这个表编入索引(IndexB,Slope,Y),这会强制我们插入此表中为O(log(n))

在我们用新记录(B',S',Y',A')填写此表后,我们检查是否已经有一个元素,使得B'=现有表的A和B!=新记录的一个'(意思是我们有一个独特的三元组)匹配斜率和Y轴截距(意味着共线)。如果是这种情况并且A&lt; B&lt; B',将计数递增1。

编辑:一个澄清的评论。我们需要确保首先将这个表“向后”填充,使所有不满足A&lt; B(&lt; C)。这可以确保在我们开始测试它们之前它们是否存在于表中。

编辑:哇我的C ++生锈了......花了一段时间。

#include <iostream>
#include <vector>
#include <set>
#include <stdlib.h>
#include <math.h>

using namespace std;

#define ADD_POINT(xparam,yparam) { point x; x.x = xparam; x.y = yparam; points.push_back(x); };

#define EPSILON .001

class line {
public:
  double slope;
  double y;
  int a;
  int b;

  bool operator< (const line &other) const{
    if(this->a < other.a)
      return true;
    else if(this->a==other.a){
      if(this->slope-other.slope < -EPSILON)
        return true;
      else if(fabs(this->slope-other.slope) < EPSILON){
        if(this->y-other.y < -EPSILON)
          return true;
        else
          return false;
      }else
        return false;
    }else
      return false;
  }

  line(double slope, double y, int a, int b){
    this->slope = slope;
    this->y = y;
    this->a = a;
    this->b = b;
  }

  line(const line &other){
    this->slope = other.slope;
    this->y = other.y;
    this->a = other.a;
    this->b = other.b;
  }
};

class point {
public:
  double x;
  double y;
};

int main(){
  vector<point> points;
  ADD_POINT(0,0);
  ADD_POINT(7,28);
  ADD_POINT(1,1);
  ADD_POINT(2,3);
  ADD_POINT(2,4);
  ADD_POINT(3,5);
  ADD_POINT(3,14);
  ADD_POINT(5,21);
  ADD_POINT(9,35);

  multiset<line> lines;
  for(unsigned int x=0;x<points.size();x++){
    for(unsigned int y=0;y<points.size();y++){
      if(x!=y){ // No lines with the same point
        point a = points[x];
        point b = points[y];
        double slope = (a.y-b.y)/(a.x-b.x);
        double yint;
        yint = a.y-a.x*slope;
        line newline(slope,yint,x,y);
        lines.insert(newline);
      } 
    }
  }

  for(multiset<line>::const_iterator p = lines.begin(); p != lines.end(); ++p){
    //cout << "Line: " << p->a << " " << p->b << " " << p->slope << " " << p->y << endl;
    line theline = *p;
    line conj(theline.slope,theline.y,theline.b,-1);
    multiset<line>::iterator it;
    pair<multiset<line>::iterator,multiset<line>::iterator> ret;
    ret = lines.equal_range(conj);
    for(it = ret.first; it!=ret.second; ++it){
      //cout << "  Find: " << it->a << " " << it->b << " " << it->slope << " " << it->y << endl;
      int a = theline.a;
      int b = theline.b;
      int c = it->b;
      if(a < b && b < c){
        cout << a << " " << b << " " << c << std::endl;
      }
    }
  }


  //cout << points[0].x << std::endl;

}

答案 5 :(得分:0)

我有这个解决方案告诉你是否有更好的,

根据它们使用x轴或任何其他所需的轴(O(n * logn))对所有点进行排序。现在,如果经过排序列表并找到在正方向或负方向上具有相同斜率的点,则需要做的就是这一点(这可以在线性时间内完成,即O(n))。让我们说你得到一个案例的m点,然后用C(m,3)递增答案。

总时间取决于您实施C(m,3)的程度

但渐近O(N logN)

编辑:在看到icCube的评论后,我意识到我们不能采取任何轴..所以对于上面定义的算法,将斜率计算点作为n个点之一(因此n次)应该是我最好的猜测。但它使算法N * N * Log(N)