欧氏几何——LeetCode 面试题 16.03. 交点 (提交双100%)

10 篇文章 0 订阅
4 篇文章 0 订阅


LeetCode每日一题打卡✔️使用一般方法拿到双100%💯
今天出了这道题,我来试着写题解✋

题目描述

题目来源:LeetCode 面试题 16.03. 交点

面试题 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} 输出: {},两条线段没有交点

提示:

坐标绝对值不会超过 2^7
输入的坐标均是有效的二维坐标

隐藏提示:
所有的线都会相交吗?什么决定两条线是否相交?
无限长的线几乎总会相交,除非它们相互平行。平行线也仍然有可能“相交”——如果它们是同一条线。这对线段来说意味着什么?
我们怎样才能找到两条线的交点。如果两条线相交,那么交点必须与它们的“无限”延伸处于同一点。这两条线之间是交点吗?
仔细考虑如何处理线段具有相同斜率和与y轴相交的情况。

来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/intersection-lcci
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解题思路:两点式判断平行+参数方程求交点

震惊!LeetCode困难难度,竟然是一道普通的中学数学题

先复习一下中学学过的两点式:
来自百度知道
上面的两点式改为乘的形式:

(y4 - y3) * (x2 - x1) == (y2 - y1) * (x4 - x3)

考虑了平行于y轴、x轴的情况也能使用。

复习一下参数方程,为了求交点:
来自LeetCode官方题解
即代码中会使用到:

double t1 = (double)(x3 * (y4 - y3) + y1 * (x4 - x3) - y3 * (x4 - x3) - x1 * (y4 - y3)) / ((x2 - x1) * (y4 - y3) - (x4 - x3) * (y2 - y1));

t1、t2的含义:
一定有且仅有一组 (t1,t2) 使得上面的二元一次方程组成立,t1​ 描述了交点在线段 (x1,y1)∼(x2,y2),t2同理。

以上为此题目需要的中学知识。下面我用伪代码把主要步骤梳理清楚。分支挺容易漏掉的,建议增加测试用例多测一下再提交。

伪代码
class Solution {
    public double[] intersection(int[] start1, int[] end1, int[] start2, int[] end2) {
		if (使用两点式,判断两线段平行,平行则为true) {
			if (不在直线上) {
				返回空
			} else if (重合) {//在直线上
				if(端点满足条件)*4 {
					返回满足条件的端点中最优的//详见完整代码(解释:重合的话有四种情况(相离、一个交点、多个交点、重合),但实际题目只要求坐标值最小的点,那就肯定在题目输入的四个点之一。)
				}
				//不重合
				返回空
				}
			}
		} else {//两线段所在的直线相交
			联立两个参数方程求解t1,t2
			
			if(t1,t2均不在[0,1]区间,即交点不在线段上) {
				返回空
			} else {//交点在线段上
				返回 ans = {x1 + t1 * (x2 - x1), y1 + t1 * (y2 - y1)};
			}
		}
    }
}

伪代码里没详细写的一个细节是平行于x轴和y轴的情况下,若与 x 轴平行,只需要判断 x 的部分,y轴同理。

完整代码
class Solution {
    // 注意:调用的前提是(xk,yk)已经在「直线」上
    // 功能:判断 (xk, yk) 是否在「线段」(x1, y1)~(x2, y2) 上
    private boolean inside(int x1, int y1, int x2, int y2, int xk, int yk) {
        return (x1 == x2 || (Math.min(x1, x2) <= xk && xk <= Math.max(x1, x2))) && (y1 == y2 || (Math.min(y1, y2) <= yk && yk <= Math.max(y1, y2)));
    }
    //更新结果为四个点中最优值
    private void update(double[] ans, double xk, double yk) {
        if (ans[0] == Double.MAX_VALUE && ans[1] == Double.MAX_VALUE || xk < ans[0] || (xk == ans[0] && yk < ans[1])){
            ans[0] = xk;
            ans[1] = yk;
        }
    }

    public double[] intersection(int[] start1, int[] end1, int[] start2, int[] end2) {
        int x1 = start1[0];
        int y1 = start1[1];
        int x2 = end1[0];
        int y2 = end1[1];
        int x3 = start2[0];
        int y3 = start2[1];
        int x4 = end2[0];
        int y4 = end2[1];

        double[] ans = new double[2];
        Arrays.fill(ans, Double.MAX_VALUE);//初始化为极大值,方便后面判断更新

        //使用两点式,判断两线段平行,平行则为true
        if ((y4 - y3) * (x2 - x1) == (y2 - y1) * (x4 - x3)) {
            //若平行,则判断 (x3, y3) 是否在「直线」(x1, y1)~(x2, y2) 上
            if ((y2 - y1) * (x3 - x1) == (y3 - y1) * (x2 - x1)) {
                boolean isInside = false;
                //如果重合,返回满足条件的端点中最优的
                //判断 (x3, y3) 是否在「线段」(x1, y1)~(x2, y2) 上
                if (inside(x1, y1, x2, y2, x3, y3)) {
                    isInside = true;
                    update(ans, (double)x3, (double)y3);
                }
                if (inside(x1, y1, x2, y2, x4, y4)) {
                    isInside = true;
                    update(ans, (double)x4, (double)y4);
                }
                if (inside(x3, y3, x4, y4, x1, y1)) {
                    isInside = true;
                    update(ans, (double)x1, (double)y1);
                }
                if (inside(x3, y3, x4, y4, x2, y2)) {
                    isInside = true;
                    update(ans, (double)x2, (double)y2);
                }
                //不重合
                if (!isInside) {
                    return new double[0];
                }
            } else {//不在直线上
                return new double[0];
            }
        } else {//两线段所在的直线相交
            //联立两个参数方程求解t1,t2
            double t1 = (double)(x3 * (y4 - y3) + y1 * (x4 - x3) - y3 * (x4 - x3) - x1 * (y4 - y3)) / ((x2 - x1) * (y4 - y3) - (x4 - x3) * (y2 - y1));
            double t2 = (double)(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 (t1 >= 0.0 && t1 <= 1.0 && t2 >= 0.0 && t2 <= 1.0) {
                ans[0] = x1 + t1 * (x2 - x1);
                ans[1] = y1 + t1 * (y2 - y1);
            } else {
                return new double[0];
            }
        }
        
        if (ans[0] == Double.MAX_VALUE && ans[1] == Double.MAX_VALUE) {
            return new double[0];
        }
        return ans;
    }
}

提交显示双100%💯。
LeetCode提交截图
复杂度分析:

时间复杂度:O(1)。

空间复杂度:O(1)。

其他解法

方法二:叉积法。使用向量,进行叉积运算取模+共线处理+计算两个三角形面积后使用定比分点公式。
时间空间复杂度也是O(1)。有兴趣自行前往官方题解,链接已附在下方参考资料章节。

参考资料:

[1]题目 LeetCode 面试题 16.03. 交点https://leetcode-cn.com/problems/intersection-lcci/
[2]交点 官方题解https://leetcode-cn.com/problems/intersection-lcci/solution/jiao-dian-by-leetcode-solution/
[3]C++ 一般式求交点 作者:前额叶没长好https://leetcode-cn.com/problems/intersection-lcci/solution/c-yi-ban-shi-qiu-jiao-dian-by-time-limit/
[4]在非欧几何中,平行线能相交,怎么解释? - psai的回答 - 知乎https://www.zhihu.com/question/28620054/answer/837114817

小声哔哔环节

为什么用伪代码呢?我的理解是,代码不多,干脆写成伪代码比较直观。因为用传统排版弄成文字段落的话,读者看层次还挺费劲的,笔者也没必要就纠结markdown的多级标题,标题层次改起来没有代码方便。
如果是项目文档则另说,这里只是写OJ题解。

另外,大家听说过非欧几何吗😏,前提是在空间曲率不为0的情况下,平行线最终会交于一点。
所以,即使我们化作宇宙中的尘埃,也许终有一天也会相遇呢。哎呀骚断腿 (╯‵□′)╯︵┻━┻

好了,觉得文章有用欢迎点赞❤️收藏⭐️关注✔️转发🔗,如有疑问请留言或从博客主页找到我联系方式,下篇文章见~

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

源计划猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值