别看这个问题的名字非常高大上,其实说的问题非常浅显易懂,就是在一个给定平面上有很多个点(给出的是它们的坐标),求出公共点最多的一条直线。(这回的开头还是直入主题吧,毕竟在大作业的面前已经焦头烂额了)
最简单的一种方法就是,每两个点作一条直线,然后遍历所有点,统计在当前直线上的点的个数:
1 class Solution { 2 public: 3 int maxPoints(vector<Point>& points) 4 { unsigned int i, j, k; 5 unsigned int result = 0; 6 7 if (points.size()==1) 8 return 1; 9 10 for (i=0;i<points.size();i++) 11 for (j=i+1;j<points.size();j++) 12 { unsigned int count = 0; 13 14 if (points[i].x==points[j].x) 15 { unsigned int x = points[i].x; 16 17 for (k=0;k<points.size();k++) 18 if (points[k].x==x) 19 count++; 20 } 21 else 22 { double x1 = points[i].x, x2 = points[j].x, 23 y1 = points[i].y, y2 = points[j].y; 24 double l_k = (y2-y1)/(x2-x1), l_b = y1-x1*l_k; 25 26 for (k=0;k<points.size();k++) 27 if (round(l_k*points[k].x+l_b)==points[k].y) 28 count++; 29 } 30 31 if (count>result) 32 result = count; 33 } 34 35 return result; 36 } 37 };
在这个算法中,每两个点确定一条直线,对应于i与j构成的循环。对每一直线遍历所有点,对应k构成的循环。这样该算法的复杂度为O(n^3)。(不过令人惊讶的是Leetcode上居然没有发生超时的情况,确实令我感到意外。不过怨念的是似乎最后一个测试样例总是有问题,不知是什么样的原因)
当然,上面这个算法的效率实际上是不高的。我们注意到,如果平面上的n个点都共线的话,那么任取两点,所得直线的斜率与截距都应当一样,于是就可以写出下面的代码:
struct line { bool vertical; double b; double k; bool operator ==(const line& another) const { return (this->vertical == another.vertical) && (this->b == another.b) && (this->k == another.k); } }; namespace std { template <> struct hash<line> { size_t operator()(const line& l) const { return (hash<bool>()(l.vertical)) ^ (hash<double>()(l.k)) ^ (hash<double>()(l.b)); } }; template <> struct hash<pair<int, int>> { size_t operator()(const pair<int, int>& p) const { return (hash<int>()(p.first)) ^ (hash<int>()(p.second)); } }; } class Solution { public: int maxPoints(vector<Point>& points) { unsigned int i, j; unordered_map<line, unordered_set<pair<int, int>>> result; for (i = 0; i<points.size(); i++) for (j = i + 1; j<points.size(); j++) { double x1 = points[i].x, x2 = points[j].x, y1 = points[i].y, y2 = points[j].y; line current_line; if (x1 == x2) current_line = { true, x1, 0 }; else { double k = (y2 - y1) / (x2 - x1); current_line = { false, y1 - x1*k, k }; } auto ptr = result.find(current_line); if (ptr == result.end()) result[current_line] = unordered_set<pair<int, int>>(); result[current_line].insert(pair<int, int>(points[i].x, points[i].y)); result[current_line].insert(pair<int, int>(points[j].x, points[j].y)); } auto ptr = result.begin(); unsigned int max_points = 0; while (ptr != result.end()) { if (ptr->second.size() > max_points) max_points = ptr->second.size(); ptr++; } return max_points; } };
(这段代码Leetcode居然不能编译,然而放到VS上却毫无问题能够正常运行,百思不得其解,今天本人的人品也是差到了极点...也或许是因为GCC和VS的STL的差别吧)
分析复杂度可知,对任两点算截距于斜率并添加到Map中,复杂度为O(n^2),之后遍历Map的过程复杂度为O(n),所以可以认为整个过程的复杂度为O(n^2),相对来说该算法的优化程度是比较高的。(这里用到的Map与Set是unordered_map与unordered_set,它们的实现方式可以简单认为是根据Hash把Value或者Key-Value Pair放入固定个数的Bucket中,如果装填因子过小或者过大就令Bucket个数折半或者翻倍,已有Bucket中Hash进行重分配。在理想情况下,插入与删除的复杂度均为O(1),遍历时间复杂度则与Bucket个数相关,但是一般装填因子都控制在一定范围内,所以可以认为Bucket数与存储数据量成正比,故复杂度为O(n))
如果使用hash_map与hash_set的话,由于hash_map与hash_set都使用平衡树,增加与删除元素复杂度均为O(n),遍历复杂度为O(nlogn),所以整个算法的复杂度会变为O(n^2logn)(实际上,hash_map与hash_set是针对存入数据需要排序的情况而设计的,但是此题中的数据不需要考虑顺序问题所以应当选择无序集合与映射。这启示我们针对不同的问题要根据需求决定采用的数据结构,这样才能最为高效地解决问题)