题目描述:
给定两条线段(表示为起点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+t△xy=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(x2−x1)y=y1+t(y2−y1)
并且将 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(x2−x1)y=y1+t1(y2−y1)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(x4−x3)y=y3+t2(y4−y3)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(x2−x1)=x3+t2(x4−x3)y1+t1(y2−y1)=y3+t2(y4−y3)
这是一个普通的二元一次方程组,我们可以得到解为:
{ 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=(x2−x1)(y4−y3)−(x4−x3)(y2−y1)x3(y4−y3)+y1(x4−x3)−y3(x4−x3)−x1(y4−y3)t2=(x4−x3)(y2−y1)−(x2−x1)(y4−y3)x1(y2−y1)+y3(x2−x1)−y1(x2−x1)−x3(y2−y1)
这里可能会有读者产生疑问:需要判定这个解的分母可能为 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)
(x2−x1)(y4−y3)−(x4−x3)(y2−y1)
若它的值为 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} ⇒⇒(x2−x1)(y4−y3)−(x4−x3)(y2−y1)=0(x2−x1)(y4−y3)=(x4−x3)(y2−y1)x4−x3y4−y3=x2−x1y2−y1
得到等式的左右两侧分别为两条直线的斜率,由于它们不平行,斜率显然不会相等,那么分母不可能为 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/
题目来源: