给定一个二维平面上的点集,如何找出位于同一条直线上点的最大数量?这是LeetCode第149题"直线上最多的点数"要解决的核心问题。乍看简单,实则暗藏玄机。
我在第一次遇到这个问题时,以为只需要计算两两点之间的斜率就能轻松解决。但实际编码时发现,浮点数精度问题、垂直线特殊情况、重复点处理等细节会让代码迅速变得复杂。经过多次调试和优化,最终总结出一套可靠的C语言解法。
最直观的解法是三重循环:对于每个点,计算它与其他所有点组成的直线,然后统计每条直线上的点数。这种方法时间复杂度高达O(n³),在LeetCode上会直接超时。
c复制// 伪代码示例(实际不可行)
int maxPoints = 0;
for (int i = 0; i < n; i++) {
for (int j = i+1; j < n; j++) {
int count = 2;
for (int k = j+1; k < n; k++) {
if (isCollinear(points[i], points[j], points[k])) {
count++;
}
}
maxPoints = max(maxPoints, count);
}
}
更聪明的做法是:对于每个点,计算它与其他所有点的斜率,用哈希表统计相同斜率的频次。这样可以将时间复杂度降到O(n²)。
关键观察点:
c复制typedef struct {
int x;
int y;
} Point;
// 简化分数表示(避免浮点精度问题)
typedef struct {
int dx;
int dy;
} Fraction;
c复制Fraction getSlope(Point p1, Point p2) {
int dx = p2.x - p1.x;
int dy = p2.y - p1.y;
// 处理垂直线
if (dx == 0) return (Fraction){0, 1};
// 处理水平线
if (dy == 0) return (Fraction){1, 0};
// 计算最大公约数
int gcd_val = gcd(dx, dy);
// 返回最简分数形式
return (Fraction){dx/gcd_val, dy/gcd_val};
}
c复制int gcd(int a, int b) {
while (b != 0) {
int temp = b;
b = a % b;
a = temp;
}
return a;
}
c复制int maxPoints(Point* points, int pointsSize) {
if (pointsSize <= 2) return pointsSize;
int max_count = 0;
for (int i = 0; i < pointsSize; i++) {
// 用于统计相同斜率的点数
Fraction* slopes = malloc((pointsSize - 1) * sizeof(Fraction));
int slope_count = 0;
int same_point = 0;
int current_max = 0;
for (int j = 0; j < pointsSize; j++) {
if (i == j) continue;
if (points[i].x == points[j].x && points[i].y == points[j].y) {
same_point++;
continue;
}
slopes[slope_count++] = getSlope(points[i], points[j]);
}
// 统计斜率频次
qsort(slopes, slope_count, sizeof(Fraction), compareFractions);
if (slope_count > 0) {
int count = 1;
for (int k = 1; k < slope_count; k++) {
if (slopes[k].dx == slopes[k-1].dx &&
slopes[k].dy == slopes[k-1].dy) {
count++;
} else {
current_max = current_max > count ? current_max : count;
count = 1;
}
}
current_max = current_max > count ? current_max : count;
}
// 加上当前点本身和重复点
current_max = current_max + 1 + same_point;
max_count = max_count > current_max ? max_count : current_max;
free(slopes);
}
return max_count;
}
c复制int compareFractions(const void* a, const void* b) {
Fraction* fa = (Fraction*)a;
Fraction* fb = (Fraction*)b;
if (fa->dx == fb->dx && fa->dy == fb->dy) return 0;
if (fa->dx * fb->dy < fb->dx * fa->dy) return -1;
return 1;
}
直接使用浮点数存储斜率会导致精度问题:
c复制double slope = (double)(p2.y - p1.y) / (p2.x - p1.x); // 不推荐
解决方案:
当输入中包含多个相同坐标的点时:
当两点x坐标相同时:
上述实现使用了排序+遍历统计频次,更高效的做法是使用哈希表:
c复制// 伪代码示例(需要实现哈希函数)
HashMap* map = createHashMap();
for (每个点) {
Fraction slope = getSlope(p1, p2);
int count = getFromHashMap(map, slope);
putToHashMap(map, slope, count + 1);
}
时间复杂度:O(n²)
空间复杂度:O(n)
验证算法正确性的关键测试场景:
c复制[(1,1),(2,2),(3,3)] → 3
c复制[(1,1),(1,1),(2,2)] → 3
c复制[(1,1),(1,2),(1,3)] → 3
c复制[(1,1),(2,2),(3,3),(0,0),(1,2)] → 4
c复制[(0,0),(0,0),(0,0)] → 3
内存管理:
边界条件:
比较函数实现:
斜率哈希:
对于大规模点集(虽然LeetCode不需要):
随机采样:
空间分割:
并行计算:
这个问题可以延伸到:
在实际工程中,类似的思路可用于: