我被问到这个面试问题(C ++,algos)并且不知道如何解决它。
给定一个数组,说包含N个不同点的笛卡尔坐标的Arr [N]计算三元组的数量(Arr [P],Arr [Q],Arr [R]),使得P <1。 Q&lt; R&lt; N和点Arr [P],Arr [Q],Arr [R]共线(即位于同一条直线上)。
有什么想法吗?我可以使用什么算法?
答案 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>
要解决:
Map<Line, Set<Point2d>>
收集一行中的所有点
正如问题本身所示。 以下问题的代码
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)