题目描述
小飞和哥哥在玩游戏,哥哥在平面坐标系中某些点放了蛋糕,每个点放一块。哥哥让小飞先拿蛋糕,但只能拿同一条直线上的蛋糕。小飞想尽量多拿,请你帮帮他。
输入格式:
- 第一行一个整数 n (1 ≤ n ≤ 500)
- 接下来 n 行,每行两个整数 x, y,表示一个点的坐标
- 保证没有重复的点
输出格式:
- 一个整数,表示一条直线能覆盖的最多的点数(即最多能拿到几块蛋糕)
样例输入:
5
1 1
2 2
3 3
9 10
10 11
示意图:("*"是蛋糕,"."是没有蛋糕的点)
* . . . . . . . . .
. * . . . . . . . .
. . * . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . *
. . . . . . . . * .
样例输出:
3
问题分析
这是一个经典的最多共线点问题。给定平面上的 n 个点,我们需要找到一条直线,使得这条直线经过的点数最多。
关键点:
- n ≤ 500,时间复杂度需要控制在 O(n²) 或更低
- 需要考虑斜率不存在的情况(垂直线)
- 需要考虑重复点(题目已说明没有重复点)
- 需要考虑浮点数精度问题
解题思路
方法一:暴力枚举 + 斜率统计(O(n²))
对于每个点 i,计算它与其他所有点的斜率:
- 以点 i 为基准点
- 统计所有以点 i 为起点的直线的斜率
- 相同斜率的点在同一条直线上
- 记录最大点数
注意:
- 斜率用分数形式 (dx, dy) 表示,约分后存储
- 垂直线斜率用特殊标记(如 (1, 0))
- 水平线斜率为 (0, 1)
- 使用最大公约数(gcd)约分
方法二:优化考虑
- 使用哈希表存储斜率及其出现次数
- 时间复杂度:O(n²),对于 n=500 完全可行(500² = 250,000)
C++代码实现
cpp
#include <iostream>
#include <vector>
#include <unordered_map>
#include <algorithm>
using namespace std;
// 简化分数表示的结构体
struct Slope {
int dx, dy;
// 重载==运算符用于哈希
bool operator==(const Slope& other) const {
return dx == other.dx && dy == other.dy;
}
};
// 自定义哈希函数
struct SlopeHash {
size_t operator()(const Slope& s) const {
return hash<long long>()((static_cast<long long>(s.dx) << 32) | s.dy);
}
};
int maxPoints(vector<vector<int>>& points) {
int n = points.size();
if (n <= 2) return n;
int maxCount = 1;
for (int i = 0; i < n; i++) {
unordered_map<Slope, int, SlopeHash> slopeCount;
int duplicate = 0; // 与点i重合的点数
int currentMax = 0;
for (int j = i + 1; j < n; j++) {
int dx = points[j][0] - points[i][0];
int dy = points[j][1] - points[i][1];
// 处理重合点
if (dx == 0 && dy == 0) {
duplicate++;
continue;
}
// 约分斜率
int g = __gcd(dx, dy);
dx /= g;
dy /= g;
// 确保斜率表示一致(统一符号)
if (dx < 0) {
dx = -dx;
dy = -dy;
} else if (dx == 0) {
dy = abs(dy);
}
Slope slope{dx, dy};
slopeCount[slope]++;
currentMax = max(currentMax, slopeCount[slope]);
}
// 当前点的最大共线点数 = 相同斜率的最大点数 + 重合点数 + 1(自身)
maxCount = max(maxCount, currentMax + duplicate + 1);
}
return maxCount;
}
int main() {
int n;
cin >> n;
vector<vector<int>> points(n, vector<int>(2));
for (int i = 0; i < n; i++) {
cin >> points[i][0] >> points[i][1];
}
cout << maxPoints(points) << endl;
return 0;
}
代码详解
1. 数据结构设计
cpp
struct Slope {
int dx, dy; // 斜率用分数形式表示
};
用分数 (dx, dy) 表示斜率,避免浮点数精度问题。
2. 斜率约分
cpp
int g = __gcd(dx, dy);
dx /= g;
dy /= g;
使用最大公约数约分,确保相同斜率的表示一致。
3. 符号统一
cpp
if (dx < 0) {
dx = -dx;
dy = -dy;
} else if (dx == 0) {
dy = abs(dy);
}
确保斜率表示一致,例如 (1, 2) 和 (-1, -2) 被视为相同斜率。
4. 处理特殊情况
- 垂直线:dx = 0,dy = 1(统一表示)
- 水平线:dx = 1,dy = 0
- 重合点:单独计数
算法复杂度分析
-
时间复杂度:O(n²)
- 外层循环:O(n)
- 内层循环:O(n)
- 哈希操作:O(1)
- 总复杂度:O(n²)
-
空间复杂度:O(n)
- 哈希表最多存储 n-1 个斜率
对于 n = 500,最坏情况操作次数约为 250,000,完全在 1 秒时间限制内。
测试样例验证
样例1
输入:
5
1 1
2 2
3 3
9 10
10 11
输出:3
解释:点 (1,1)、(2,2)、(3,3) 共线,斜率为 1。
样例2
输入:
4
0 0
1 1
2 2
3 3
输出:4
解释:所有点都在直线 y = x 上。
样例3
输入:
3
0 0
0 1
0 2
输出:3
解释:所有点都在垂直线 x = 0 上。
常见错误与注意事项
-
浮点数精度问题
- 不要用浮点数计算斜率
- 使用分数形式 (dx, dy) 表示
-
重复点处理
- 题目说没有重复点,但代码中仍做了处理
- 实际比赛中可能有重复点测试数据
-
边界情况
- n = 1 时,输出应为 1
- n = 2 时,输出应为 2
- 所有点都相同时,输出应为 n
-
哈希表性能
- 使用自定义哈希函数提高性能
- 确保哈希冲突较少
优化思路
进一步优化
- 提前终止:如果剩余点数 + 当前最大点数 ≤ 已找到的最大点数,可以提前终止
- 并行计算:对于大数据量,可以考虑并行处理
- 空间优化:使用数组代替哈希表(需要处理斜率映射)
更优算法
对于 n > 1000 的情况,可以考虑更优的算法,如:
- 随机算法(随机选取直线)
- 分治算法
- 扫描线算法
总结
本题是一个经典的几何问题,考察了:
- 哈希表的使用
- 分数表示斜率避免精度问题
- 最大公约数的应用
- 边界情况的处理
关键点在于用分数 (dx, dy) 表示斜率,并通过约分和符号统一来确保相同斜率的表示一致。对于 n ≤ 500 的数据范围,O(n²) 的算法完全足够。
扩展练习
- LeetCode 149. Max Points on a Line - 相同问题
- 判断三点是否共线 - 简化版本
- 求所有共线点对 - 统计所有共线关系
- 最大共线点集(允许误差) - 加入浮点数误差容忍
掌握这个问题的解法,对于理解计算几何和哈希表应用非常有帮助。