2026年字节跳动春招算法岗的题已经陆续流出来了,3月20日这一场的第四题《小红的红色直线》讨论度挺高。这道题表面是个几何题,实际上考的是哈希去重、边界处理和一点点数论基本功,属于典型的“看着害怕、想明白后代码很短”的题。我这两天把网上流传的回忆版整理成了一个可以复现的版本,完整跑通了Java、C++、Python三份代码,今天把题目理解、推导过程、踩坑点和三种语言实现一次性写清楚。准备冲算法岗笔试的朋友,尤其是奔着字节去的,建议认真看完。
1. 这道题到底在问什么
1.1 还原后的题目描述
这是根据多份面经和讨论帖整理的回忆版,题目名字和个别细节可能和原题有出入,但核心考点是一样的。
题目描述:在二维平面上有 n 个红色点,第 i 个点的坐标是 (x_i, y_i),坐标都是整数,保证没有重复点。一条直线被称为“红色直线”,当且仅当它至少经过两个给定的红色点。请问一共有多少条不同的红色直线。
输入格式:
- 第一行一个整数 n,表示点的个数。
- 接下来 n 行,每行两个整数 x_i 和 y_i,表示第 i 个点的坐标。
输出格式:
- 一个整数,表示不同红色直线的条数。
数据范围(回忆版常见范围):
- 1 ≤ n ≤ 5000
- |x_i|, |y_i| ≤ 10^5
为什么要特别强调数据范围?因为这道题的思路完全取决于 n 的量级。n 如果在 5000 以内,O(n^2) 的枚举是可行的;n 如果到 10^5,O(n^2) 直接爆炸,题目性质就完全不同了。从春招笔试的难度来看,n 取 3000 到 5000 这个区间最合理,既能考察哈希和去重能力,又不至于让大部分人连暴力都写不出来。
1.2 样例分析
先给两个简单样例,跑通之后对题意的理解就稳了。
样例一:
3 0 0 1 1 2 2三个点都在 y = x 这条直线上。虽然 C(3,2) = 3 个点对能确定三组“两点连线”,但连出来是同一条直线,所以答案应该是 1。
样例二:
4 0 0 1 0 0 1 1 1这是平面上的四个正方形顶点。任意两个点确定的直线都不共线,或者说没有出现“三个点落在同一条直线上”的情况,所以 C(4,2) = 6 条直线全部不同,答案是 6。
如果只看这两个样例,很多人会觉得这题就是求“有多少对点的连线不重复”,但真正的难点藏在“三个及以上点共线”这种情况里。如果一条直线上有 4 个点,那么这 4 个点两两之间能组成 6 个点对,这 6 个点对都确定同一条直线,必须只算一次。
1.3 这题真正想考什么
字节的算法岗笔试很少出纯数学题,这道题的包装是“几何”,内核其实是三个点:
- 几何对象的哈希表示:怎么用整数精确表示一条直线,而不是用浮点数。
- 集合去重:如何保证同一条直线只被计数一次。
- 边界处理:负坐标、垂直直线、水平直线、全部点共线等特殊情况。
如果你在面试复盘时能把这三点说出来,面试官会觉得你真的吃透了题目,而不是背了个模板。
2. 从暴力到正解:完整思路推导
2.1 为什么不能用浮点斜率
很多人第一反应是算斜率,把每条直线表示成 (斜率, 截距),然后放到 set 里去重。比如一条直线经过点 p1 和 p2,斜率为 (y2-y1)/(x2-x1),截距为 y1 - 斜率*x1。这个思路方向是对的,但直接用 double 会死得很惨。
第一个问题是精度。double 的有效数字大概是 15 到 16 位十进制。坐标差如果到 10^5,那么两条斜率极其接近的直线在 double 里可能被误判成同一条。比如 (0,0) 和 (100000,1) 连成的直线,与 (0,0) 和 (100001,1) 连成的直线,斜率差大概在 10^-10 量级,double 勉强能分出来,但如果你再叠加上截距的计算误差,结果就不敢保证了。
第二个问题是垂直直线。x1 == x2 时斜率是无穷大,你不得不单独写一个 if 分支。这个分支本身不复杂,但它破坏了代码的统一性,而且一旦某个坐标差是 0,除法会直接出问题。
第三个问题是同一直线的不同表示。一条直线上有多个点时,你从不同的点对去计算斜率和截距,得到的浮点结果会有细微差别,比如 (0,1) 和 (2,2) 算出的斜率可能是 0.5,而 (2,2) 和 (4,3) 算出的斜率可能是 0.5000000001。把它们当成不同直线去重,答案就错了。
正确的做法是用整数精确表示几何方向,完全抛弃浮点。
2.2 最常见的错误:方向数累加后除以 2
我在网上看到不少人在讨论这道题时提出了一个看起来很聪明的方案:枚举每个点作为基准点,统计从它出发有多少个不同的方向,把所有点的方向数加起来,最后除以 2。
这个方案初听很有道理。每条直线经过两个点,直线 AB 在以 A 为基准时被数一次,在以 B 为基准时被数一次,所以总数除以 2 就是直线数。问题出在哪?出在“一条直线上有 3 个及以上点”的情况。
举一个最简单的例子:平面上有 4 个点,全部落在同一条直线上。以每个点为基准,它到另外 3 个点的方向都是同一个,所以每个点贡献 1 个方向,4 个点合计贡献 4,除以 2 得到 2。但正确答案是 1。这条直线实际上被数了 4 次,而不是 2 次。
更准确地说,如果有 k 个点共线,那么这条直线会被 k 个基准点各数一次,总贡献是 k,而不是 2。