Leetcode 面试题16.03 - 交点

Leetcode 面试题16.03 - 交点

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

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

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

文章目录

  • Leetcode 面试题16.03 - 交点
    • 1 平面几何法
      • 1.1 如何表示点在直线上
      • 1.2 如何计算直线交点
      • 1.3 如何判断点在线段范围内
    • 2 向量法
      • 2.1 如何判断直线平行
      • 2.2 如何判断线段相交
      • 2.3 如何求交点

首先,确定思路:

Created with Raphaël 2.2.0 输入 判断是否平行 判断是否重合 取最小重合点 退出 计算直线交点 判断交点是否在线段内 取交点 yes no yes no yes no

1 平面几何法

学过初中数学的人都会认为:啊,简单!但很快你就会发现并不是那么回事,需要考虑的情况很多。

从数学角度,我们可以比较轻松的得到上述的流程图,但是从代码实现的角度我们必须要考虑到几个重要的问题:

1.1 如何表示点在直线上

数学理论知识告诉我们,一条直线可以表示为:
y = k x + b 或 a x + b y + c = 0 y = kx + b \quad 或 \quad ax + by + c = 0 y=kx+bax+by+c=0
但是,明显我们不知道方程中的参数值,我们已知的信息只有直线上两个点的坐标。幸运的是,数学老师教过 两点确定一条直线,并且考虑到直线有垂直于x轴和垂直于y轴两种特殊情况,用 ( x 1 , y 1 ) , ( x 2 , y 2 ) (x_1,y_1), (x_2,y_2) (x1,y1),(x2,y2) 表示一条直线上的两点,则直线可以表示为: x = x 1 + t ( x 2 − x 1 ) , y = y 1 + t ( y 2 − y 1 ) x = x_1 + t(x_2-x_1), y=y_1+t(y2-y1) x=x1+t(x2x1),y=y1+t(y2y1)

1.2 如何计算直线交点

仅考虑两直线相交的情况,用 ( x 1 , y 1 ) , ( x 2 , y 2 ) (x_1,y_1), (x_2,y_2) (x1,y1),(x2,y2) 表示第一条直线上的两点, ( x 3 , y 3 ) , ( x 4 , y 4 ) (x_3,y_3) ,(x_4,y_4) (x3,y3),(x4,y4) 表示第二条直线上的两点。则有:

x = x 1 + t 1 ( x 2 − x 1 ) , y = y 1 + t 1 ( y 2 − y 1 ) x = x_1 + t_1(x_2-x_1), y=y_1+t_1(y2-y1) x=x1+t1(x2x1),y=y1+t1(y2y1)

x = x 3 + t 2 ( x 4 − x 3 ) , y = y 3 + t 2 ( y 4 − y 3 ) x = x_3 + t_2(x_4-x_3) ,y=y_3+t_2(y4-y3) x=x3+t2(x4x3),y=y3+t2(y4y3)

考虑到不平行直线有且只有唯一交点,因此联立方程组必能得到 t 1 , t 2 t_1,t_2 t1,t2 的唯一解

t 1 = ( x 3 − x 1 ) ( y 4 − y 3 ) − ( y 3 − y 1 ) ( x 4 − x 3 ) ( x 2 − x 1 ) ( y 4 − y 3 ) − ( x 4 − x 3 ) ( y 2 − y 1 ) t_1=\frac{(x_3-x_1)(y_4-y_3)-(y_3-y_1)(x_4-x_3)}{(x_2-x_1)(y_4-y_3)-(x_4-x_3)(y_2-y_1)} t1=(x2x1)(y4y3)(x4x3)(y2y1)(x3x1)(y4y3)(y3y1)(x4x3)

t 2 = ( x 1 − x 3 ) ( y 2 − y 1 ) − ( y 1 − y 3 ) ( x 2 − x 1 ) ( x 2 − x 1 ) ( y 4 − y 3 ) − ( x 4 − x 3 ) ( y 2 − y 1 ) t_2=\frac{(x_1-x_3)(y_2-y_1)-(y_1-y_3)(x_2-x_1)}{(x_2-x_1)(y_4-y_3)-(x_4-x_3)(y_2-y_1)} t2=(x2x1)(y4y3)(x4x3)(y2y1)(x1x3)(y2y1)(y1y3)(x2x1)

1.3 如何判断点在线段范围内

如图所示为点在线段上的三种情况,可以明确,当 t ∈ [ 0 , 1 ] t\in[0,1] t[0,1] ,点 ( x , y ) (x,y) (x,y) 在线段 ( x 1 , y 1 ) (x_1,y_1) (x1,y1) —— ( x 2 , y 2 ) (x_2,y_2) (x2,y2)
Leetcode 面试题16.03 - 交点_第1张图片
AC代码如下:

class Solution {
public:
    vector<double> intersection(vector<int>& start1, vector<int>& end1, vector<int>& start2, vector<int>& end2) {
        x1 = start1[0], y1 = start1[1], x2 = end1[0], y2 = end1[1];
        x3 = start2[0], y3 = start2[1], x4 = end2[0], y4 = end2[1];
        if(isparallal()){
            if(ispointonline(x1, y1, x3, y3, x4, y4)) update(x1, y1);
            if(ispointonline(x2, y2, x3, y3, x4, y4)) update(x2, y2);
            if(ispointonline(x3, y3, x1, y1, x2, y2)) update(x3, y3);
            if(ispointonline(x4, y4, x1, y1, x2, y2)) update(x4, y4);
        }
        else {
            double t1 = (double)((x3 - x1) * (y4 - y3) - (y3 - y1) * (x4 - x3)) 
              								/ ((x2 - x1) * (y4 - y3) - (x4 - x3) * (y2 - y1));
            double t2 = (double)((x1 - x3) * (y2 - y1) - (y1 - y3) * (x2 - x1)) 
              								/ ((x4 - x3) * (y2 - y1) - (x2 - x1) * (y4 - y3));
            if (t1 >= 0.0 && t1 <= 1.0 && t2 >= 0.0 && t2 <= 1.0) {
                res = {x1 + t1 * (x2 - x1), y1 + t1 * (y2 - y1)};
            }
        }
        return res;
    }

private:
    int x1,y1,x2,y2,x3,y3,x4,y4;
    vector<double> res;

    /* 判断是否平行 */
    bool isparallal(){
        return (x2 - x1) * (y4 - y3) == (y2 - y1) * (x4 - x3);
    }

    /*  在平行基础上,判断点(x,y)是否在线段(sx,sy)——(ex,ey)上 */
    bool ispointonline(int x, int y, int sx, int sy, int ex, int ey){
        if(sx == ex && x == ex) return isbetween(y, sy, ey);    // 垂直于x轴
        if(sy == ey && y == ey) return isbetween(x, sx, ex);    // 垂直于y轴
        double t1 = (double)(x - sx) / (ex - sx);
        double t2 = (double)(y - sy) / (ey - sy);
        return t1 == t2 && t1 >= 0.0 && t1 <= 1.0;
    }

    /* 判断x是否在范围内 */
    bool isbetween(int x, int x1, int x2){
        if(x1 > x2) swap(x1, x2);
        return x >= x1 && x <= x2;
    }

    /* 重合时,更新最小(x,y) */
    void update(int x, int y){
        if (res.empty()){
            res.push_back((double)x);
            res.push_back((double)y);
        }
        else if ((double)x < res[0] || ((double)x == res[0] && (double)y < res[1])){
            res[0] = (double)x;
            res[1] = (double)y;
        }
    }
};

2 向量法

转换一下思路,用向量的思路进行思考

2.1 如何判断直线平行

向量内积(点乘)为0,表示向量垂直;向量外积(叉乘)为0,表示向量平行。用 a ⃗ = ( x 1 , y 1 ) \vec{a}=(x_1, y_1) a =(x1,y1), b ⃗ = ( x 2 , y 2 ) \vec{b}=(x_2, y_2) b =(x2,y2) 表示两个向量,则有:

a ⃗ ⊥ b ⃗ ⇔ a ⃗ ⋅ b ⃗ = x 1 x 2 + y 1 y 2 = 0 \vec{a}\perp \vec{b} \Leftrightarrow \vec{a} \cdot \vec{b}=x_1x_2+y_1y_2=0 a b a b =x1x2+y1y2=0

a ⃗ ∥ b ⃗ ⇔ a ⃗ × b ⃗ = x 1 y 2 − x 2 y 1 = 0 \vec{a}\parallel \vec{b} \Leftrightarrow \vec{a} \times \vec{b}=x_1y_2-x_2y_1=0 a b a ×b =x1y2x2y1=0

2.2 如何判断线段相交

Leetcode 面试题16.03 - 交点_第2张图片
如图所示,当且仅当P、Q在直线MN两侧,且M、N在直线PQ两侧时,线段PQ与MN相交。

那么我们的问题就转变为如何借助向量,判断两点是否在直线两侧。考虑下图:
Leetcode 面试题16.03 - 交点_第3张图片
向量的外积具有一定的几何意义,当 a ⃗ × b ⃗ = x 1 y 2 − x 2 y 1 > 0 \vec{a} \times \vec{b}=x_1y_2-x_2y_1>0 a ×b =x1y2x2y1>0 b ⃗ \vec{b} b a ⃗ \vec{a} a 逆时针方向;当 a ⃗ × b ⃗ = x 1 y 2 − x 2 y 1 < 0 \vec{a} \times \vec{b}=x_1y_2-x_2y_1<0 a ×b =x1y2x2y1<0 b ⃗ \vec{b} b a ⃗ \vec{a} a 顺时针方向。因此可得,

M 、 N 在 P Q 两 侧 ⇔ ( P M ⃗ × P Q ⃗ ) ( P N ⃗ × P Q ⃗ ) ≤ 0 M、N在PQ两侧 \Leftrightarrow (\vec{PM} \times \vec{PQ}) (\vec{PN} \times \vec{PQ})\leq0 MNPQ(PM ×PQ )(PN ×PQ )0

则有

M N 与 P Q 相 交 ⇔ ( P M ⃗ × P Q ⃗ ) ( P N ⃗ × P Q ⃗ ) ≤ 0 且 ( M P ⃗ × M N ⃗ ) ( M Q ⃗ × M N ⃗ ) ≤ 0 MN与PQ相交 \Leftrightarrow (\vec{PM} \times \vec{PQ}) (\vec{PN} \times \vec{PQ})\leq 0 且 (\vec{MP} \times \vec{MN}) (\vec{MQ} \times \vec{MN})\leq 0 MNPQ(PM ×PQ )(PN ×PQ )0(MP ×MN )(MQ ×MN )0

2.3 如何求交点

向量的外积,除了可表示向量的方向之外,还可用于计算三角形的面积。

∵ ∣ A B ⃗ × A C ⃗ ∣ = ∣ A B ⃗ ∣ ⋅ ∣ A C ⃗ ∣ ⋅ s i n ( ∠ B A C ) \because |\vec{AB} \times \vec{AC}| = |\vec{AB}| \cdot |\vec{AC}| \cdot sin(\angle{BAC}) AB ×AC =AB AC sin(BAC)

∴ S ( △ A B C ) = 1 2 ⋅ ∣ A B ⃗ ∣ ⋅ ∣ A C ⃗ ∣ ⋅ s i n ( ∠ B A C ) = 1 2 ⋅ ∣ A B ⃗ × A C ⃗ ∣ \therefore S(△ABC)=\frac{1}{2} \cdot |\vec{AB}| \cdot |\vec{AC}| \cdot sin(\angle{BAC})=\frac{1}{2} \cdot |\vec{AB} \times \vec{AC}| S(ABC)=21AB AC sin(BAC)=21AB ×AC

交点在直线中的位置,也可转换为面积的位置,最终通过计算叉积得到,若 M ( x 1 , y 1 ) M(x_1, y_1) M(x1,y1), N ( x 2 , y 2 ) N(x_2,y_2) N(x2,y2) ,则可计算出点 O ( x , y ) O(x,y) O(x,y) 的坐标
Leetcode 面试题16.03 - 交点_第4张图片
AC代码如下:

class Solution {
public:
    vector<double> intersection(vector<int>& start1, vector<int>& end1, vector<int>& start2, vector<int>& end2) {
        pmx = start2[0] - start1[0], pmy = start2[1] - start1[1];
        pnx = end2[0] - start1[0], pny = end2[1] - start1[1];
        pqx = end1[0] - start1[0], pqy = end1[1] - start1[1];
        mpx = -pmx, mpy = -pmy;
        mqx = end1[0] - start2[0], mqy = end1[1] - start2[1];
        mnx = end2[0] - start2[0], mny = end2[1] - start2[1];
        npx = -pnx, npy = -pny;
        nqx = end1[0] - end2[0], nqy = end1[1] - end2[1];
        if(cross(pqx, pqy, mnx, mny) == 0){
            if(ispointonline(start1, start2, end2)) update(start1);
            if(ispointonline(end1, start2, end2)) update(end1);
            if(ispointonline(start2, start1, end1)) update(start2);
            if(ispointonline(end2, start1, end1)) update(end2);
        }
        else if(isintersect()){
            double lambda = fabs((double)cross(mpx, mpy, mqx, mqy) / cross(npx, npy, nqx, nqy));
            double k = lambda / (lambda + 1);
            res.push_back(start2[0] + k * (end2[0] - start2[0]));
            res.push_back(start2[1] + k * (end2[1] - start2[1]));
        }
        return res;
    }

private:
    int pmx, pmy, pnx, pny, pqx, pqy, mpx, mpy, mqx, mqy, mnx, mny, npx, npy, nqx, nqy;
    vector<double> res;

    /* 计算向量乘积 */
    int cross(int x1, int y1, int x2, int y2){
        return x1 * y2 - x2 * y1;
    }

    /* 判断两线段是否相交 */
    bool isintersect(){
        return cross(pmx, pmy, pqx, pqy) * cross(pnx, pny, pqx, pqy) <= 0 
            && cross(mpx, mpy, mnx, mny) * cross(mqx, mqy, mnx, mny) <= 0;
    }

    /*  在平行基础上,判断点(x,y)是否在线段(sx,sy)——(ex,ey)上 */
    bool ispointonline(vector<int>& point, vector<int>& start, vector<int>& end){
        int x = point[0], y = point[1];
        int sx = start[0], sy = start[1], ex = end[0], ey = end[1];
        if(sx == ex && x == ex) return isbetween(y, sy, ey);    // 垂直于x轴
        if(sy == ey && y == ey) return isbetween(x, sx, ex);    // 垂直于y轴
        double t1 = (double)(x - sx) / (ex - sx);
        double t2 = (double)(y - sy) / (ey - sy);
        return t1 == t2 && t1 >= 0.0 && t1 <= 1.0;
    }

    /* 判断x是否在范围内 */
    bool isbetween(int x, int x1, int x2){
        if(x1 > x2) swap(x1, x2);
        return x >= x1 && x <= x2;
    }

    /* 重合时,更新最小(x,y) */
    void update(vector<int>& point){
        int x = point[0], y = point[1];
        if (res.empty()){
            res.push_back((double)x);
            res.push_back((double)y);
        }
        else if ((double)x < res[0] || ((double)x == res[0] && (double)y < res[1])){
            res[0] = (double)x;
            res[1] = (double)y;
        }
    }
};

你可能感兴趣的:(Leetcode,leetcode)