跳转至

极角排序、斜率与距离度量 (Polar Angles & Distance Metrics)

在许多平面几何与组合优化问题中,点之间的方向关系(角度、斜率)与空间度量(曼哈顿距离、切比雪夫距离)扮演着决定性角色。

本章系统梳理极角排序的实现技巧、斜率有理数无损哈希、曼哈顿与切比雪夫距离的等价转化定理,以及在 LeetCode 经典高频题中的巧妙应用。


1. 极角排序 (Polar Angle Sort)

极角排序是指以某一点 \(O\) 为原点,将平面上的其余各点按照极角(与正向 X 轴的夹角)从小到大进行排序。

1.1 实现方式一:标准库 atan2 排序

C++ 标准库函数 std::atan2(double y, double x) 返回点 \((x, y)\) 的极角,取值范围为 \([-\pi, \pi]\):

#include <vector>
#include <cmath>
#include <algorithm>

struct Point {
    double x, y;
};

void sortByAtan2(Point origin, std::vector<Point>& pts) {
    std::sort(pts.begin(), pts.end(), [&](const Point& a, const Point& b) {
        double angle_a = std::atan2(a.y - origin.y, a.x - origin.x);
        double angle_b = std::atan2(b.y - origin.y, b.x - origin.x);
        return angle_a < angle_b;
    });
}
  • 优点:能够天然处理全象限(包括负半轴),代码极短;
  • 缺点:涉及浮点数三角函数运算,存在纳秒级常数开销与潜在浮点误差。

1.2 实现方式二:象限 + 叉积精确整数排序

若坐标均为整数且追求 100% 精确与极致效率,可使用象限划分结合叉积:

// 获取点所属象限 (1, 2, 3, 4)
int getQuadrant(const Point& p) {
    if (p.x > 0 && p.y >= 0) return 1;
    if (p.x <= 0 && p.y > 0) return 2;
    if (p.x < 0 && p.y <= 0) return 3;
    return 4; // p.x >= 0 && p.y < 0
}

bool polarCompare(const Point& a, const Point& b) {
    int qa = getQuadrant(a);
    int qb = getQuadrant(b);
    if (qa != qb) return qa < qb;
    // 同象限内通过叉积判断: 叉积大于 0 说明 a 在 b 的顺时针方向 (极角更小)
    return 1LL * a.x * b.y - 1LL * a.y * b.x > 0;
}

2. 斜率表示与有理数无损哈希

在求解共线点问题(如 LeetCode 149)时,直接用 double 计算斜率 \(k = \frac{\Delta y}{\Delta x}\) 会因精度问题导致哈希表无法精准匹配,同时无法自然处理垂直线段(\(\Delta x = 0\))。

2.1 最大公约数约分化简

我们将斜率表示为一个规范化的二元组 \((\Delta x, \Delta y)\):

  1. 计算 \(\Delta x = x_2 - x_1\) 和 \(\Delta y = y_2 - y_1\);
  2. 计算最大公约数 \(g = \gcd(|\Delta x|, |\Delta y|)\);
  3. 约分:\(\Delta x \leftarrow \frac{\Delta x}{g}, \quad \Delta y \leftarrow \frac{\Delta y}{g}\);
  4. 符号规范化(唯一性保证):
  5. 若 \(\Delta x < 0\),两边同乘 \(-1\)(保证分母始终非负);
  6. 若 \(\Delta x == 0\),统一规范化为 \((0, 1)\);
  7. 若 \(\Delta y == 0\),统一规范化为 \((1, 0)\)。
#include <numeric>
#include <utility>

std::pair<int, int> getNormalizedSlope(int x1, int y1, int x2, int y2) {
    int dx = x2 - x1;
    int dy = y2 - y1;
    if (dx == 0) return {0, 1};
    if (dy == 0) return {1, 0};
    int g = std::gcd(dx, dy);
    dx /= g;
    dy /= g;
    if (dx < 0) {
        dx = -dx;
        dy = -dy;
    }
    return {dx, dy};
}

3. 曼哈顿距离与切比雪夫距离转换

3.1 距离定义

对于平面上两点 \(P_1(x_1, y_1)\) 与 \(P_2(x_2, y_2)\):

  • 曼哈顿距离(\(L_1\) 范数):\(d_1(P_1, P_2) = |x_1 - x_2| + |y_1 - y_2|\)
  • 切比雪夫距离(\(L_\infty\) 范数):\(d_\infty(P_1, P_2) = \max(|x_1 - x_2|, |y_1 - y_2|)\)

3.2 坐标旋转等价变换定理

曼哈顿转切比雪夫

将原坐标 \((x, y)\) 旋转 \(45^\circ\) 并放大 \(\sqrt{2}\) 倍,新坐标为:

\[ (x', y') = (x + y, x - y) \]

则原坐标系下的曼哈顿距离等于新坐标系下的切比雪夫距离:

\[ |x_1 - x_2| + |y_1 - y_2| = \max(|x_1' - x_2'|, |y_1' - y_2'|) \]

切比雪夫转曼哈顿

将原坐标 \((x, y)\) 转换为:

\[ (x', y') = \left(\frac{x + y}{2}, \frac{x - y}{2}\right) \]

则原坐标系下的切比雪夫距离等于新坐标系下的曼哈顿距离。

核心应用:

曼哈顿距离的两个维度相互耦合(绝对值相加),难以独立维护;通过转换为切比雪夫距离,问题变为 \(\max(|x_1' - x_2'|, |y_1' - y_2'|)\),\(X\) 轴与 \(Y\) 轴完全解耦,只需分别求出一维坐标的最值差即可!


4. LeetCode 经典真题精选与深度精析

  • LeetCode 149 - 直线上最多的点数 (Max Points on a Line) 困难

    提示

    固定单点 + 斜率有理数约分哈希统计。

    • 枚举每个点 \(i\) 作为直线的基准点;
    • 遍历其余所有点 \(j > i\),利用 \(\gcd\) 约分将斜率规范化为二元组 \((dx, dy)\);
    • 哈希表统计每种斜率出现的频次,取全局最大值 \(+ 1\)。
    • 时间复杂度 \(\mathcal{O}(N^2)\),空间复杂度 \(\mathcal{O}(N)\)。
  • LeetCode 1610 - 可见点的最大数目 (Maximum Number of Visible Points) 困难

    提示

    atan2 极角转换 + 数组倍长环形滑动窗口。

    • 与观察者重合的点天然可见,单独计数。
    • 对其余所有点计算相对极角 \(\theta = \text{atan2}(\Delta y, \Delta x)\),排序;
    • 处理环形跨越边界技巧:将全数组复制一份追加到末尾,每个追加角加上 \(2\pi\)(\(360^\circ\));
    • 双指针滑动窗口:维护窗口使得 \(angles[r] - angles[l] \le \text{angle}\),求最大窗口长度并加上重合点数。时间复杂度 \(\mathcal{O}(N \log N)\)。
  • LeetCode 963 - 最小面积矩形 II (Minimum Area Rectangle II) 中等

    提示

    矩形对角线中点与长度哈希。

    • 矩形的两条对角线中点相同且长度相等。
    • 枚举所有点对 \((P_i, P_j)\),计算对角线长度平方 \(len = \text{dist}^2(P_i, P_j)\) 与中点坐标 \((x_1 + x_2, y_1 + y_2)\);
    • 用哈希表以 (中点坐标, 长度) 为键,存储所有满足该条件的对角线点对;
    • 同一键下的任意两条对角线必然构成矩形,计算面积取最小值。时间复杂度 \(\mathcal{O}(N^2)\)。
  • LeetCode 1266 - 访问所有点的最小时间 (Minimum Time Visiting All Points) 简单

    提示

    切比雪夫距离直接应用。

    • 每次移动可以水平、垂直或对角线移动一格。从 \((x_1, y_1)\) 移动到 \((x_2, y_2)\) 的最少步数恰好就是两点间的切比雪夫距离 \(\max(|x_1 - x_2|, |y_1 - y_2|)\)。
    • 顺序遍历累加相邻两点的切比雪夫距离即可。时间复杂度 \(\mathcal{O}(N)\),空间复杂度 \(\mathcal{O}(1)\)。