微软面试题:点集中的直线
前言:只是为了加深自己对面试题的领悟力,如果有做的错的,请指出,感激。
微软面试题:从给定点集中,找到所有的在一条直线的三个点,用三元组记录。
算法概要:最一般的算法是遍历所有的点,三重循环,判断三个点的斜率是否相等或者向量的叉积是否为零。时间复杂为O(N3)。
我好像找到了一个上限是O(N2LogN)的算法。
假设平面上有三个点A,B,C。三点互相连接的话一定有三条直线。
A 点存储 Kab,Kac.(代表斜率的意思)
B 点存储Kbc
C 点什么也不存储。
一条直线连接两个点,因此A点存储直线AB的斜率时,B点就不用存储了。如果A点存储的Kab和Kac相等,那么就代表A,B,C在一条直线上就可以形成一个三元组。
如果再在平面上添加一个点D,应该是依次求D与A,B,C连线直线的斜率,并存储到相应的位置。
A 点存储 Kab,Kac,Kad(代表斜率的意思)
B 点存储Kbc,Kbd
C 点存储Kcd
D 点什么也不存储。
点集A和B,B的初始值为空,A保存平面中所有点集,每次从A中取出一个点p,与B中的所有点求斜率后放入点集B,直到A为空。
求两点斜率和保存斜率问题
假设平面中点的数量为n,对于A点要求(n-1)次的斜率,对于B点要求(n-2)次的斜率,……最后一个点不用求斜率,因此求斜率要O(N2)的时间复杂度。
对于集合A中的第一个点,它关联的斜率就是K12,K13…K1n,共计n-1个点,第二个点关联的斜率为K23,K24…K2n-1共计n-2个点….在算法实现时,可以利用这个规律。
查找某一点关联的斜率集合中,斜率相同的元素。
求完斜率之后对A点关联的斜率进行遍历查找,如果存在相同的斜率那么就是代表找到了一个三元组(要考虑3个以上的点在一条直线的情况)。如果不排序的话时间复杂度是
O(N2),总的时间复杂度就是 1的平方+2的平方+3的平方+…=(N-1)的平方,时间复杂度大概也是O(N3)的级别的。
如果排序的话查找斜率相同的元素数时间复杂度为O(N+m)N是斜率集合的长度,假设斜率集合中某一个斜率元素相同的个数为K,它可以组合的三元组为K*(k-1)/2的个数,m是集合中所有的三元组的个数。M的值还真不好估计,K=N时取最大,数量级为O(N2).一般情况下不会大于N(这个没有数学依据,我瞎说的)。
如果采用快速(希尔,归并)排序的话,排序的时间复杂度O(NlogN)。
如果不考虑O(N+m)m量级N2的情况。对集合A所有点相关联的斜率排序的时间复杂度为:1log1+2log2+3log3+….+(N-1)log(N-1)<log(n-1)(1+2+3+4+…..+N-1),因此时间复杂度的上限为O(N2LogN).
算法的C#代码实现部分,请先看下面的图(可以另存为图片,这样看的比较舒服一些)。
Trip是一个结构体,代表三元组。
PointFactory是一个专门负责生产平面上点集的类,是一个完全的静态类,对外提供两个接口:一个是点集的数量,另外一个是根据索引给出点集的XY坐标,如果索引大于点集数量则返回false,否则返回true并返回XY坐标。
IFindLineHelper是一个接口,主要有一个函数,List<Trip> GetTripTuple(int start, int mainIndex); mainIndex 是平面上点集合指向某点的一个索引,要求的就是这个点与集合中满足某些条件的点链接直线段的斜率,条件就是:集合中点集的索引index>= start,(主要就是前面所说的从集合A中取出某一点p,放到B中,mainIndex就是点p,start之后就是要与点p计算斜率的点,mainIndex之前的点已经求出),返回的三元组。
FindLineHelper是IFindLineHelper实现类,它主要私有方法:一个是计算与这个点关联的斜率,另一个就是对这些斜率进行排序(要注意他们的斜率和索引信息保持一致),因此成员变量除了一个斜率数组外slopeArr,还有一个索引数组IndexArr。ERROR是个常量代表当两个直线斜率的差的绝对值小于ERROR时,就认为斜率是一样的,避免计算误差。点的坐标XY的类型是Int主要是为了计算方便,如果是double类型需要考虑两个点一样,斜率不存在的判断比int要复杂一些。
PointCollect类代表对点集可能存在的索引操作,找到点集的在一条直线的三元组只是他的一个功能之一,所以通过IFindLineHelper接口聚合到这个PointCollect类中。详细见UML,里面有两个函数,一个是O(N3)实现,一个是IFindLineHelper提供的,经过测试三者一致。
FindLineHelper和PointCollect都在实际计算过程都使用了PointFactory的接口,所以在UML图使用关联,trIp是一样的为了避免图复杂,就没有画,可以参考类的依赖关系图。
IFindLineHelper和PointCollect相关代码
public interface IFindLineHelper { /// <summary> ///在point集合中 /// 第start个至最后一个点所有的在一条直线的三元组 /// </summary> /// <param name="start"></param> /// <param name="mainIndex"></param> /// <returns></returns> List<Trip> GetTripTuple(int start, int mainIndex); } public class FindLineHelper : IFindLineHelper { private const double ERROR = 1.0e-10; private int[] indexArr; private double[] slopeArr; /// <summary> /// 指向indexArr和slopeArr 最后一个有效数据 /// 有效数据的长度Length=arrSp+1; /// </summary> private int arrSp; public FindLineHelper() { indexArr =new int[PointFactory.PointCount]; slopeArr=new double[PointFactory.PointCount]; arrSp = -1; } public List<Trip> GetTripTuple(int start, int mainIndex) { arrSp = -1; Build_I_S_Arr(start,mainIndex); QuickSortArr(0,arrSp); int i,j,k; i = mainIndex; List<Trip> listTrip = new List<Trip>(); for(j=0;j<arrSp;j++ ) { k=j+1; while(k<=arrSp &&Math.Abs(slopeArr[j]-slopeArr[k])<ERROR) { listTrip.Add( new Trip ( mainIndex,indexArr[j],indexArr[k]) ); k++; } } return listTrip; } /// <summary> /// 表示对indexArr和slopeArr进行赋值运算, /// </summary> /// <param name="start"></param> /// <param name="mainIndex"></param> private void Build_I_S_Arr(int start, int mainIndex) { int pa = mainIndex; int pax=0, pay=0; bool pointExist = false; pointExist = PointFactory.GetPointXY(pa, ref pax, ref pay); if (!pointExist) { Console.WriteLine("取数据出错,请注意Build_I_S_Arr函数"); return; } int pb=start; int pbx=0,pby=0; while (PointFactory.GetPointXY(pb, ref pbx, ref pby)) { if (pax == pbx && pay == pby) { Console.WriteLine("出现相同点,会影响数据"); } else if (pax == pbx) { //出现斜率不存在的点 arrSp++; indexArr[arrSp] = pb; slopeArr[arrSp] = double.MaxValue; } else { arrSp++; indexArr[arrSp] = pb; slopeArr[arrSp] = ((double)pay - pby) / (pax - pbx); } pb++; } } /// <summary> /// 对slopeArr进行快速排序,排序的同时需要对indexArr进行相应的操作 /// 保证索引信息与斜率一一对应。 /// </summary> /// <param name="begin"></param> /// <param name="end"></param> private void QuickSortArr(int begin,int end) { if (begin < end) { int keyIndex=indexArr[begin]; double keySlope = slopeArr[begin]; int i = begin, j = end; while (i < j) { while (i < j) { if (slopeArr[j] > keySlope) j--; else if (slopeArr[j] == keySlope && indexArr[j] >= keyIndex) { j--; } else break; } if (i < j) { indexArr[i] = indexArr[j]; slopeArr[i] = slopeArr[j]; } while (i < j) { if (slopeArr[i] < keySlope) i++; else if (slopeArr[i] == keySlope && indexArr[i] <= keyIndex) { i++; } else { break; } } if (i < j) { indexArr[j] = indexArr[i]; slopeArr[j] = slopeArr[i]; } } indexArr[i] = keyIndex; slopeArr[i] = keySlope; QuickSortArr(begin,i-1); QuickSortArr(i + 1, end); } } } public class PointCollect { IFindLineHelper _findLineHelper; int pointCount; public PointCollect() { _findLineHelper=new FindLineHelper(); pointCount=PointFactory.PointCount; } public void PintPointInLine() { int i=0,count=0; for(i=1;i<pointCount-2;i++) { List<Trip> listTrip=_findLineHelper.GetTripTuple(i,i-1); foreach(Trip tp in listTrip) { tp.print(); count++; } } Console.WriteLine(count); Console.ReadKey(); } public void ThreeCycleForPointInLine() { int count=0; for (int i = 0; i < pointCount - 2; i++) for (int j = i + 1; j < pointCount - 1; j++) for (int k = j + 1; k < pointCount; k++) { int xi=0,yi=0,xj=0,yj=0,xk=0,yk=0; PointFactory.GetPointXY(i,ref xi,ref yi); PointFactory.GetPointXY(j,ref xj,ref yj); PointFactory.GetPointXY(k,ref xk,ref yk); int Xij = xi-xj; int Yij = yi-yj; int Xjk = xk-xj; int Yjk = yk-yj; if ((Xij * Yjk - Yij * Xjk) == 0) { count++; Console.WriteLine("({0},{1},{2})", i, j, k); } } Console.WriteLine(count); Console.ReadKey(); } } public struct Trip { public int i, j, k; public Trip(int x, int y, int z) { i = x; j = y; k = z; } public void print() { Console.WriteLine("({0},{1},{2})", i, j, k); } }
- 微软面试题“井盖为什么是圆的?”最佳答案
- 2014微软面试题
- 微软、google、雅虎、百度等各大著名公司的经典面试题
- 微软笔试面试题集锦
- 你在船上,把一只箱子抛起来,水平面会升高还是下降? - Google, 谷歌,百度,baidu,阿里巴巴,alibaba,微软,华为,huawei面试题
- 23. 微软面试题:跳台阶问题
- 算法--微软面试题:求一个整数数组元素间最小差值
- 江湖传闻的微软面试题
- 微软面试题1.把二元查找树转变成排序的双向链表
- 微软面试题:某水王的发帖数超过总贴数的一半,找出之
- 微软面试题:有100万个数字(1到9),其中只有1个数字重复2次,如何快速找出该数字
- 微软面试题之数字谜题方案
- 微软面试题答案
- 经典面试题(二)附答案 算法+数据结构+代码 微软Microsoft、谷歌Google、百度、腾讯
- 微软的面试题及答案-超变态但是很经典
- 微软面试题3
- 微软,Google面试题 (10) —— 在排序数组中寻找两个数的和等于给定数
- 微软,Google面试题 (29) —— 调整数组顺序使奇数位于偶数前面
- 微软面试题 07012012[2]
- 微软面试题 07022012 [3]