面试题 16.03. 交点

题目描述:

给定两条线段(表示为起点start = {X1, Y1}和终点end = {X2, Y2}),如果它们有交点,请计算其交点,没有交点则返回空值。

要求浮点型误差不超过10^-6。若有多个交点(线段重叠)则返回 X 值最小的点,X 坐标相同则返回 Y 值最小的点。

示例 1:

输入:
line1 = {0, 0}, {1, 0}
line2 = {1, 1}, {0, -1}
输出: {0.5, 0}

示例 2:

输入:
line1 = {0, 0}, {3, 3}
line2 = {1, 1}, {2, 2}
输出: {1, 1}

示例 3:

输入:
line1 = {0, 0}, {1, 1}
line2 = {1, 0}, {2, 1}
输出: {},两条线段没有交点

解题思路: 参数方程

预备知识

在数学上,我们一般会使用直线的方程加上一些限制,来表示一条线段。常用的表示直线的方法有如下几种:

  • 斜截式,用两个参数分别表示斜率和截距,即:

                 y = k x + b y = kx + b y=kx+b

  • 截距式,用两个参数分别表示在 x 轴和 y 轴上的截距,即:

                 x a + y b = 1 \frac{x}{a}+\frac{y}{b}=1 ax+by=1

  或者也可以写成:
                 a x + b y + c = 0 ax + by + c = 0 ax+by+c=0

  • 参数方程式,用三个参数分别表示直线上的一个已知点以及参数 t t t,即:

                 { x = x 0 + t △ x y = y 0 + t Δ y \left\{\begin{array}{l}x=x 0+t \triangle x \\ y=y 0+t \Delta y\end{array}\right. {x=x0+txy=y0+tΔy

那么对于本题而言,我们使用哪一种方法比较好呢?我们需要依次考虑上述的方法在表示「线段」时有没有潜在的缺陷:

  • 对于「斜截式」,它本身就有一个很严重的缺陷:它无法表示与 yy 轴平行的直线或线段(即直线 x = c x=c x=c);

  • 对于「截距式」,它可以表示任意的直线,但在表示线段时非常不直观;

  • 对于「参数方程式」,它可以表示任意的直线,并且它非常适合用于表示线段。假设我们给定两个点 ( x 1 , y 1 ) (x_1, y_1) (x1,y1)以及 ( x 2 , y 2 ) (x_2, y_2) (x2,y2),我们只需要令:

                 { x = x 1 + t ( x 2 − x 1 ) y = y 1 + t ( y 2 − y 1 ) \left\{\begin{array}{l}x=x_{1}+t\left(x_{2}-x_{1}\right) \\ y=y_{1}+t\left(y_{2}-y_{1}\right)\end{array}\right. {x=x1+t(x2x1)y=y1+t(y2y1)

  并且将 t 限制在 [0, 1]的范围内,就可以表示端点为 ( x 1 , y 1 ) (x_1, y_1) (x1,y1)以及 ( x 2 , y 2 ) (x_2, y_2) (x2,y2)的线段,十分方便。

因此,使用「参数方程式」表示线段是最适合本题的。在下面的题解中,我们会给出使用「参数方程式」解决本题的方法。

说明

为了叙述方便,我们设一条线段的两个端点分别为 ( x 1 , y 1 ) (x_1, y_1) (x1,y1)以及 ( x 2 , y 2 ) (x_2, y_2) (x2,y2),另一条线段的两个端点分别为 ( x 3 , y 3 ) (x_3, y_3) (x3,y3) 以及 ( x 4 , y 4 ) (x_4, y_4) (x4,y4)

初中数学告诉我们:平面上的两条直线如果不平行,那么它们一定相交,并且有唯一的交点。那么我们首先就来处理两条线段不平行的情况:

  • 写出这两条线段所在直线的参数方程;
  • 联立这两个参数方程求出交点;
  • 判断这个交点是否在线段上。

思路很简单,但需要小心一些,因为有不少的数学推导。我们一步一步来:

  • 写出这两条线段所在直线的参数方程:

对于线段 ( x 1 , y 1 ) ∼ ( x 2 , y 2 ) (x1, y1) \sim (x2, y2) (x1,y1)(x2,y2),它的参数方程为:

            { x = x 1 + t 1 ( x 2 − x 1 ) y = y 1 + t 1 ( y 2 − y 1 ) t 1 ∈ [ 0 , 1 ] \left\{\begin{array}{l}x=x_{1}+t_{1}\left(x_{2}-x_{1}\right) \\ y=y_{1}+t_{1}\left(y_{2}-y_{1}\right)\end{array} \quad t_{1} \in[0,1]\right. {x=x1+t1(x2x1)y=y1+t1(y2y1)t1[0,1]

对于线段 ( x 3 , y 3 ) ∼ ( x 4 , y 4 ) (x3, y3) \sim (x4, y4) (x3,y3)(x4,y4),它的参数方程为:

            { x = x 3 + t 2 ( x 4 − x 3 ) y = y 3 + t 2 ( y 4 − y 3 ) t 2 ∈ [ 0 , 1 ] \left\{\begin{array}{l}x=x_{3}+t_{2}\left(x_{4}-x_{3}\right) \\ y=y_{3}+t_{2}\left(y_{4}-y_{3}\right)\end{array} \quad t_{2} \in[0,1]\right. {x=x3+t2(x4x3)y=y3+t2(y4y3)t2[0,1]

联立这两个参数方程求出交点:

我们先忽略 t 1 , t 2 ∈ [ 0 , 1 ] t 1 t1, t2 \in [0, 1]t1 t1,t2[0,1]t1, 的限制,求出它们的值之后再进行判断。联立得到的方程组为:

         { x 1 + t 1 ( x 2 − x 1 ) = x 3 + t 2 ( x 4 − x 3 ) y 1 + t 1 ( y 2 − y 1 ) = y 3 + t 2 ( y 4 − y 3 ) \left\{\begin{array}{l}x_{1}+t_{1}\left(x_{2}-x_{1}\right)=x_{3}+t_{2}\left(x_{4}-x_{3}\right) \\ y_{1}+t_{1}\left(y_{2}-y_{1}\right)=y_{3}+t_{2}\left(y_{4}-y_{3}\right)\end{array}\right. {x1+t1(x2x1)=x3+t2(x4x3)y1+t1(y2y1)=y3+t2(y4y3)

这是一个普通的二元一次方程组,我们可以得到解为:

        { t 1 = x 3 ( y 4 − y 3 ) + y 1 ( x 4 − x 3 ) − y 3 ( x 4 − x 3 ) − x 1 ( y 4 − y 3 ) ( x 2 − x 1 ) ( y 4 − y 3 ) − ( x 4 − x 3 ) ( y 2 − y 1 ) t 2 = x 1 ( y 2 − y 1 ) + y 3 ( x 2 − x 1 ) − y 1 ( x 2 − x 1 ) − x 3 ( y 2 − y 1 ) ( x 4 − x 3 ) ( y 2 − y 1 ) − ( x 2 − x 1 ) ( y 4 − y 3 ) \left\{\begin{array}{l}t_{1}=\frac{x_{3}\left(y_{4}-y_{3}\right)+y_{1}\left(x_{4}-x_{3}\right)-y_{3}\left(x_{4}-x_{3}\right)-x_{1}\left(y_{4}-y_{3}\right)}{\left(x_{2}-x_{1}\right)\left(y_{4}-y_{3}\right)-\left(x_{4}-x_{3}\right)\left(y_{2}-y_{1}\right)} \\ t_{2}=\frac{x_{1}\left(y_{2}-y_{1}\right)+y_{3}\left(x_{2}-x_{1}\right)-y_{1}\left(x_{2}-x_{1}\right)-x_{3}\left(y_{2}-y_{1}\right)}{\left(x_{4}-x_{3}\right)\left(y_{2}-y_{1}\right)-\left(x_{2}-x_{1}\right)\left(y_{4}-y_{3}\right)}\end{array}\right. {t1=(x2x1)(y4y3)(x4x3)(y2y1)x3(y4y3)+y1(x4x3)y3(x4x3)x1(y4y3)t2=(x4x3)(y2y1)(x2x1)(y4y3)x1(y2y1)+y3(x2x1)y1(x2x1)x3(y2y1)

这里可能会有读者产生疑问:需要判定这个解的分母可能为 0 吗?答案是不需要的,这是因为这两条线段对应的直线一定有唯一 的交点,也就是说,一定有且仅有一组 ( t 1 , t 2 ) (t_1, t_2) (t1,t2)使得上面的二元一次方程组成立, t 1 t_1 t1描述了交点在线段 ( x 1 , y 1 ) ∼ ( x 2 , y 2 ) (x1, y1) \sim (x2, y2) (x1,y1)(x2,y2)所在直线上的位置, t 2 t_2 t2描述了交点在线段 ( x 3 , y 3 ) ∼ ( x 4 , y 4 ) (x3, y3) \sim (x4, y4) (x3,y3)(x4,y4) 所在直线上的位置。

并且,我们也可以用数学推导出分母不可能为 0。以 t 1 t_1 t1为例,分母为:
        ( x 2 − x 1 ) ( y 4 − y 3 ) − ( x 4 − x 3 ) ( y 2 − y 1 ) \left(x_{2}-x_{1}\right)\left(y_{4}-y_{3}\right)-\left(x_{4}-x_{3}\right)\left(y_{2}-y_{1}\right) (x2x1)(y4y3)(x4x3)(y2y1)

若它的值为 0,那么有:

       ( x 2 − x 1 ) ( y 4 − y 3 ) − ( x 4 − x 3 ) ( y 2 − y 1 ) = 0 ⇒ ( x 2 − x 1 ) ( y 4 − y 3 ) = ( x 4 − x 3 ) ( y 2 − y 1 ) ⇒ y 4 − y 3 x 4 − x 3 = y 2 − y 1 x 2 − x 1 \begin{aligned} &\left(x_{2}-x_{1}\right)\left(y_{4}-y_{3}\right)-\left(x_{4}-x_{3}\right)\left(y_{2}-y_{1}\right)=0 \\ \Rightarrow &\left(x_{2}-x_{1}\right)\left(y_{4}-y_{3}\right)=\left(x_{4}-x_{3}\right)\left(y_{2}-y_{1}\right) \\ \Rightarrow & \frac{y_{4}-y_{3}}{x_{4}-x_{3}}=\frac{y_{2}-y_{1}}{x_{2}-x_{1}} \end{aligned} (x2x1)(y4y3)(x4x3)(y2y1)=0(x2x1)(y4y3)=(x4x3)(y2y1)x4x3y4y3=x2x1y2y1

  得到等式的左右两侧分别为两条直线的斜率,由于它们不平行,斜率显然不会相等,那么分母不可能为 0。

更进一步:

https://leetcode-cn.com/problems/intersection-lcci/solution/jiao-dian-by-leetcode-solution/


代码:

class Solution:
    def intersection(self, start1: List[int], end1: List[int], start2: List[int], end2: List[int]) -> List[float]:
        # 判断 (xk, yk) 是否在「线段」(x1, y1)~(x2, y2) 上
        # 这里的前提是 (xk, yk) 一定在「直线」(x1, y1)~(x2, y2) 上
        def inside(x1, y1, x2, y2, xk, yk):
            # 若与 x 轴平行,只需要判断 x 的部分
            # 若与 y 轴平行,只需要判断 y 的部分
            # 若为普通线段,则都要判断
            return (x1 == x2 or min(x1, x2) <= xk <= max(x1, x2)) and (y1 == y2 or min(y1, y2) <= yk <= max(y1, y2))

        def update(ans, xk, yk):
            # 将一个交点与当前 ans 中的结果进行比较
            # 若更优则替换
            return [xk, yk] if not ans or [xk, yk] < ans else ans

        x1, y1 = start1
        x2, y2 = end1
        x3, y3 = start2
        x4, y4 = end2

        ans = list()
        # 判断 (x1, y1)~(x2, y2) 和 (x3, y3)~(x4, y3) 是否平行
        if (y4 - y3) * (x2 - x1) == (y2 - y1) * (x4 - x3):
            # 若平行,则判断 (x3, y3) 是否在「直线」(x1, y1)~(x2, y2) 上
            if (y2 - y1) * (x3 - x1) == (y3 - y1) * (x2 - x1):
                # 判断 (x3, y3) 是否在「线段」(x1, y1)~(x2, y2) 上
                if inside(x1, y1, x2, y2, x3, y3):
                    ans = update(ans, x3, y3)
                # 判断 (x4, y4) 是否在「线段」(x1, y1)~(x2, y2) 上
                if inside(x1, y1, x2, y2, x4, y4):
                    ans = update(ans, x4, y4)
                # 判断 (x1, y1) 是否在「线段」(x3, y3)~(x4, y4) 上
                if inside(x3, y3, x4, y4, x1, y1):
                    ans = update(ans, x1, y1)
                # 判断 (x2, y2) 是否在「线段」(x3, y3)~(x4, y4) 上
                if inside(x3, y3, x4, y4, x2, y2):
                    ans = update(ans, x2, y2)
            # 在平行时,其余的所有情况都不会有交点
        else:
            # 联立方程得到 t1 和 t2 的值
            t1 = (x3 * (y4 - y3) + y1 * (x4 - x3) - y3 * (x4 - x3) - x1 * (y4 - y3)) / ((x2 - x1) * (y4 - y3) - (x4 - x3) * (y2 - y1))
            t2 = (x1 * (y2 - y1) + y3 * (x2 - x1) - y1 * (x2 - x1) - x3 * (y2 - y1)) / ((x4 - x3) * (y2 - y1) - (x2 - x1) * (y4 - y3))
            # 判断 t1 和 t2 是否均在 [0, 1] 之间
            if 0.0 <= t1 <= 1.0 and 0.0 <= t2 <= 1.0:
                ans = [x1 + t1 * (x2 - x1), y1 + t1 * (y2 - y1)]

        return ans

参考链接:

https://leetcode-cn.com/problems/intersection-lcci/solution/jiao-dian-by-leetcode-solution/


题目来源:

https://leetcode-cn.com/problems/intersection-lcci

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值