题目描述

✅ 335. 路径交叉

image-20260929094853563

image-20260929094853685

image-20260929094853781

image-20260929094853930

题意分析

从原点出发,按北、西、南、东循环行走,每步长度都是正数,判断新走出的线段是否碰到此前的路径。与非相邻历史线段的端点接触、共线重叠都算相交;连续两步正常共用的连接端点不算。

解法:三种几何相交判定

核心思路

[!blue]

每走一步都固定左转 $90$ 度,所以不必把当前线段与所有旧线段逐一比较。相邻边只共用连接点,隔一条的边与当前边平行且被正长度的边隔开;最早从第四条边开始才可能自交。

只考虑第一次相交之前的路径:固定左转会形成向外扩张的螺旋,或在某次转向后进入内缩。持续内缩时,最近一圈的边挡在更早路径前面,向外伸得过长会先撞到 i - 3;从外扩转入内缩的入口处,还可能接触 i - 4,或越过入口碰到 i - 5。若要先到达更早的边,就必须先穿过这些近处边界。因此每轮只需检查当前边与 i - 3、i - 4、i - 5 的三种局部关系,一旦发现便立即返回。

为推导条件,把当前边及向前五条边的长度依次记作 a、b、c、d、e、f,分别对应 distance[i] 到 distance[i - 5]。平移、旋转不改变相交关系,可以把当前边的起点放在 (0, 0),并令它向北走到 (0, a)。

与 i - 3 垂直相交:这条旧边位于高度 c,横向范围为 [-b, d - b]。当前边要到达高度 c,需要 a >= c;旧边要覆盖横坐标 $0$,需要 d - b >= 0,即 b <= d。这正是代码的第一组条件。

与 i - 4 共线相接或重叠:这条旧边的横坐标是 d - b,纵向范围是 [c - e, c]。它与当前边共线要求 b == d;两个纵向区间有重叠还需要 a >= c - e,即 a + e >= c。两者缺一不可。

与 i - 5 垂直相交:这条旧边位于高度 c - e,横向范围为 [d - b - f, d - b]。它的高度要落在当前边的 [0, a] 内,需要 c >= e 且 a + e >= c;它的横向范围要包含 $0$,需要 b <= d 且 b + f >= d。合起来就是第三组的四个条件。

每组条件都在检查交点是否同时落在两条有限线段上,不能只判断它们所在的直线相交。所有比较保留等号,以覆盖端点恰好接触;第二、三组分别需要至少五条、六条边,必须先判断 i >= 4、i >= 5。

解题步骤

  1. 从第四条边开始扫描。
  2. 检查当前边与三条之前的边是否穿越。
  3. 边数足够时检查四条之前的共线情况。
  4. 边数足够时检查五条之前的内缩相交,全部未命中则继续。

代码实现

class Solution {
    public boolean isSelfCrossing(int[] distance) {
        for (int i = 3; i < distance.length; i++) {
            // 第一类:当前边与三条之前的边垂直相交。
            if (distance[i] >= distance[i - 2] && distance[i - 1] <= distance[i - 3]) {
                return true;
            }

            // 第二类:当前边与四条之前的边共线接触或重叠。
            if (i >= 4
                    && distance[i - 1] == distance[i - 3]
                    && distance[i] + distance[i - 4] >= distance[i - 2]) {
                return true;
            }

            // 第三类:由外扩转为内缩时,检查与五条之前的边相交。
            if (i >= 5
                    && distance[i - 2] >= distance[i - 4]
                    && distance[i] + distance[i - 4] >= distance[i - 2]
                    && distance[i - 1] <= distance[i - 3]
                    && distance[i - 1] + distance[i - 5] >= distance[i - 3]) {
                return true;
            }
        }

        return false;
    }
}
func isSelfCrossing(distance []int) bool {
    for i := 3; i < len(distance); i++ {
        // 第一类:当前边与三条之前的边垂直相交。
        if distance[i] >= distance[i-2] &&
            distance[i-1] <= distance[i-3] {
            return true
        }

        // 第二类:当前边与四条之前的边共线接触或重叠。
        if i >= 4 &&
            distance[i-1] == distance[i-3] &&
            distance[i]+distance[i-4] >= distance[i-2] {
            return true
        }

        // 第三类:由外扩转为内缩时,检查与五条之前的边相交。
        if i >= 5 &&
            distance[i-2] >= distance[i-4] &&
            distance[i]+distance[i-4] >= distance[i-2] &&
            distance[i-1] <= distance[i-3] &&
            distance[i-1]+distance[i-5] >= distance[i-3] {
            return true
        }
    }
    return false
}

复杂度分析

  • 时间复杂度:$O(n)$,每条边做固定次数比较。
  • 空间复杂度:$O(1)$,无需记录全部坐标。

关键点总结

[!green]

  • 检查的是第一次相交,此前路径尚未自交。
  • 三种形态对应不同历史边,不能只保留普通穿越。
  • 下标保护与各形态所需边数对应。

易错点总结

[!yellow]

  • 只检查与 i-3 的关系:会漏掉共线和内缩情况。
  • 共线条件放宽为不等号:两条平行边未必处在同一条直线上。
  • 改为严格不等号:漏掉恰好接触。
  • 未检查 i≥4、i≥5:短路径会访问不存在的历史项。

相似题目

题目 难度 关联与区别
面试题 16.03. 交点 困难 线段相交判定提供几何基础,本题还利用固定转向限制,只需关注少数相邻历史线段。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2024/18461088
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!