全栈开发知识库

107.直线上最多的点数

题目

给你一个数组 points ,其中 points[i] = [xi, yi] 表示 X-Y 平面上的一个点。求最多有多少个点在同一条直线上。

思路

核心思路:固定一个点,统计其他点相对于它的斜率

1. 为什么固定一个点?

如果一条直线经过多个点,那么这条直线上的所有点相对于这条直线上的任意一个点,斜率都相同(或垂直)。所以我们可以枚举每一个点作为基准点,然后计算其他所有点相对于这个基准点的斜率,斜率相同的点就在同一条直线上。

2. 如何处理重复点?

如果有重复的点,它们相对于基准点的斜率相同,但它们本身就是同一个位置。我们需要单独统计与基准点重合的点的个数,因为这些点必然和基准点在一条直线上(任何经过基准点的直线都经过它们)。在处理斜率时,把这些重合点排除在外,最后加到结果里。

3. 如何表示斜率以避免浮点误差?

斜率 dy/dx 如果用浮点数表示,可能会因精度问题导致误判(比如 1/3 和 2/6 理论上相同,但浮点数可能不同)。更好的方法是使用最简分数来表示斜率,即用 (dx, dy) 的最简整数比(分子分母互质,且统一符号约定,比如规定分母为正,或分子分母同号)。这样可以用字符串或 Pair 作为哈希表的键。 - 对于水平线(dy=0):斜率表示为 (0, 1) 或其他约定。 - 对于垂直线(dx=0):斜率表示为 (1, 0)。

4. 具体步骤

  • 初始化最大点数 maxCount = 0。
  • 遍历每个点 i 作为基准点:
    • 初始化哈希表 slopeMap,记录每个斜率出现的次数(不包括基准点自身和重合点)。
    • 初始化 samePointCount = 1(基准点自身算一个)。
    • 遍历每个点 j(j != i):
      • 如果 points[j] 与基准点坐标相同,则 samePointCount++。
      • 否则计算相对坐标 dx = points[j].x - points[i].x,dy = points[j].y - points[i].y。
      • 化简 (dx, dy) 为最简形式:求出 gcd(|dx|, |dy|),然后 dx /= g, dy /= g。为了统一符号,可以规定 dx 为正(如果 dx==0 则 dy 为正),或者规定 (dx, dy) 的第一项非负。
      • 将化简后的 (dx, dy) 作为键存入哈希表,计数加 1。
    • 遍历完所有 j 后,哈希表中的最大值加上 samePointCount 就是以基准点为起点的某条直线上的最大点数。
    • 更新全局 maxCount。
  • 返回 maxCount。
class Solution {
    fun maxPoints(points: Array<IntArray>): Int {
        if (points.size <= 2) return points.size

        var ans = 0

        for (i in points.indices) {
            // 以 points[i] 作为起点时,
            // 记录每种斜率出现了多少次
            val map = HashMap<String, Int>()

            // 重复点数量
            var same = 0

            // 经过当前起点的最多点数(不含重复点时)
            var maxCount = 0

            for (j in i + 1 until points.size) {
                var dx = points[j][0] - points[i][0]
                var dy = points[j][1] - points[i][1]

                // 如果两个点完全一样,单独计数
                if (dx == 0 && dy == 0) {
                    same++
                    continue
                }

                // 处理竖直线
                if (dx == 0) {
                    dy = 1
                }
                // 处理水平线
                else if (dy == 0) {
                    dx = 1
                } else {
                    // 约分,保证同一条线的斜率写法一致
                    val g = gcd(kotlin.math.abs(dx), kotlin.math.abs(dy))
                    dx /= g
                    dy /= g

                    // 统一符号,保证 (1, -1) 和 (-1, 1) 视为同一个方向
                    if (dx < 0) {
                        dx = -dx
                        dy = -dy
                    }
                }

                val key = "$dy/$dx"
                val count = map.getOrDefault(key, 0) + 1
                map[key] = count
                maxCount = maxOf(maxCount, count)
            }

            // 当前起点本身要算 1 个
            // same 是重复点,也都在这条线上
            ans = maxOf(ans, maxCount + same + 1)
        }

        return ans
    }

    private fun gcd(a: Int, b: Int): Int {
        var x = a
        var y = b
        while (y != 0) {
            val t = x % y
            x = y
            y = t
        }
        return x
    }
}

复杂度

  • 时间复杂度: O(n²),每个基准点需要遍历其他所有点。
  • 空间复杂度: O(n),哈希表最多存储 n-1 个斜率。


  • 邮箱 :charon.chui@gmail.com
  • Good Luck!
本页由 Markdown 自动渲染 · 共 628 篇笔记