《程序员面试金典(第6版)》面试题 16.03. 交点(直线的一般式方程,克莱姆法则,行列式,C++)

news2024/11/26 22:26:25

题目描述

给定两条线段(表示为起点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
  • 输入的坐标均是有效的二维坐标

解题思路与代码

这道题,乍一看是不是很简单,方法就是像初高中的一道数学题。让你去判断两条直线是否具有交点

但是呢,这道题是让你去判断两条线段是否具有交点。并且,如果有多个交点,返回最小的那一个。(在多个交点中,有且只有一个交点,它的x的值最小,那么返回这个x最小的交点。如果交点的x值都相同,则返回y值最小的交点)

不要小看线段与直线的区别,就这一点区别,让整道题的难度激增。

并且,关于直线的方程式有好多种,比如一般式方程啦,点斜式方程啦,斜截式方程啦,截距式方程啦,参数式方程啦,选择用哪一个,这也是重中之重。因为这些方程式各有优缺点。选错了,你解题的步骤又可能激增,然后等于是难上加难再加难。对就是这样。

我这边,特地的选择了一种,最好理解的方式来解决这道题。也就是使用一般式方程进行求解。这种方式最大的好处就是,写出来的代码简单易懂。

并且,这道题实际上是很偏数学的,数学不好,或者说数学与代码的结合能力差,也是根本不可能做的出来的。

如果不是面试游戏公司,应该不会出这样的代数题来为难大家吧,hhhhh。

废话不多说了,开始带大家解题吧。

方法一 : 直线的一般式方程求解

首先做这道题,需要你有一定的数学储备知识。不过你没有也没关系,就听我讲就完事了。

基础知识

首先我们解决这道题采用的是直线的一般式方程,也就是说形如Ax + By + C = 0;用到了这个方程的两个结论。

  • 一个结论就是,如果两条直线平行,则 A1B2 = A2B1

  • 其次就是,任何直线Ax + By + C = 0 中的A,B,C,都可以用两个点的横轴坐标这么去表示:

    • A = y2 - y1;
    • B = x1 - x2;
    • C = x2y1 - x1y2;
  • 如果你对这两个结论感到疑惑了话,可以去看这个文章,去补充自己的基础知识: 直线的一般式方程

还有一个知识就是关于克莱姆法则,这个涉及到大学的线性代数中的行列式的一点基础知识。也不难。如果你想要具体了解什么是克莱姆法则的话,可以去看这篇文章去补充相关的基础知识:克莱姆法则

  • 如果大家懒的看这个百度百科的克莱姆法则,你也可以看看我说的简化版的。

  • 克莱姆法则(Cramer’s Rule)是一种用于求解线性方程组的方法,适用于具有唯一解的线性方程组。克莱姆法则使用行列式(determinants)来求解方程组,它的基本思想是将原线性方程组的系数矩阵通过替换列的方法,变换成相应的行列式,然后通过计算这些行列式的值来求解未知数。

  • 对于一个 n 阶线性方程组,如果系数行列式不为零(即方程组具有唯一解),那么可以使用克莱姆法则求解。具体求解方法是将系数矩阵的第 i 列替换为方程组的右侧常数项构成的列向量,然后计算新的行列式,再将这个行列式与原系数行列式之比作为未知数 x_i 的解。

  • 那么对于这道题来说,我们有4个点,也就是两条线段。我们可以把它先理解为两条直线,那么就会有两条一般式的直线方程。也就组成了一个二元的方程组。

    • A1x + B1y + C1 = 0; => A1x + B1y = -C1;(方程组右侧是-C1)
    • A2x + B2y + C2 = 0; => A2x + B2y = -C2;(方程组右侧是-C2)
  • 在每一个方程里只有两个未知数,所以这个二元一次方程组的系数矩阵(记作:D)为:

    |A1 B1|
    |A2 B2|
    
  • 计算行列式的方法是主对角线(从左上到右下)的乘积 - 副对角线(从左下到右上)的乘积。所以D这个系数矩阵的值为 :A1B2 - A2B1;

  • 我们将第一列替换为常数项列向量,得到矩阵:

| -c1  b1 |
| -c2  b2 |
  • 设 Dx 为这个矩阵的行列式值,即 Dx = -c1 * b2 - (-c2 * b1)。那么解 x1 = Dx / D。

  • 同理,我们将第二列替换为常数项列向量,得到矩阵:

    | a1  -c1 |
    | a2  -c2 |
    
  • 设 Dy 为这个矩阵的行列式值,即 Dy = a1 * c2 - a2 * c1。那么解 x2 = Dy / D。

  • 我们可以把这组解x1,x2看做是两条直线的交点的横纵坐标,x1 = x, x2 = y,

    • 所以 x = Dx/D
    • 所以 y = Dy/D

好,知道这么多数学知识,就够我们去解决这道代码题的了。还是强调一下,直线的一般式方程求解是众多直线方程中最好理解的一种了。但就这一种也够复杂的了,如果不是面游戏公司,注重几何的,没必要再深究这道题的其他解法。

解题步骤

对于这道题,我们需要写这么几个函数,分别是:

  • 计算方程参数ABC的函数
  • 寻找当线段平行时是否存在符合题目的交点的函数
  • 判断交点是否在两条线段上的函数
  • 然后就是用主函数集合这么几个函数来求得最终的解。
    • 我们在主函数里先这么去写逻辑,首先判断俩直线是否平行,如果平行则,调用寻找当线段平行时是否存在符合题目的交点的函数,去找符合的点,找不到则返回空
    • 其次用计算方程参数ABC的函数去计算两天线段的一般式方程的ABC参数分别是多少,分别存储在两个double的vector中。
    • 克莱姆法则求出可能交点,再用判断交点是否在两条线段上的函数判断交点是否在两条线段上,若在,返回交点,否则返回空
    • 至此本题解完。

具体实现请看代码:

class Solution {
public:
    vector<double> intersection(vector<int>& start1, vector<int>& end1, vector<int>& start2, vector<int>& end2) {
        //由一般式方程Ax + By + C = 0 的结论可知 A = y2 - y1; B = x1 - x2; C = x2y1 - x1y2;
        int A1 = end1[1] - start1[1];
        int B1 = start1[0] - end1[0];
        int A2 = end2[1] - start2[1];
        int B2 = start2[0] - end2[0];
        //如果线段平行,则去寻找是否有这样一个符合条件的点,找到则返回交点,否则返回空
        if(A1 * B2 == A2 * B1) return findPointOnTheParallelLine(start1,end1,start2,end2);
        //获取两条直线方程的参数ABC
        vector<double> p1 = getParm(start1,end1);
        vector<double> p2 = getParm(start2,end2);
        //根据克莱姆法则计算可能交点(x,y)的坐标;
        double x = (p2[2] * p1[1] - p1[2] * p2[1]) / (p1[0] * p2[1] - p2[0] * p1[1]);
        double y = (p1[2] * p2[0] - p2[2] * p1[0]) / (p1[0] * p2[1] - p2[0] * p1[1]);
        vector<double> result{x,y};
        return (isIntersectionPoint(result,start1,end1) && isIntersectionPoint(result,start2,end2)) ? result : vector<double>{};
    }
    vector<double>findPointOnTheParallelLine(vector<int>& start1, vector<int>& end1, vector<int>& start2, vector<int>& end2){
        vector<vector<double>> result;
        if(isIntersectionPoint(start1,start2,end2)) result.push_back(vector<double>{double(start1[0]), double(start1[1])});
        if(isIntersectionPoint(start2,start1,end1)) result.push_back(vector<double>{double(start2[0]), double(start2[1])});
        if(isIntersectionPoint(end1,start2,end2)) result.push_back(vector<double>{double(end1[0]), double(end1[1])});
        if(isIntersectionPoint(end2,start1,end1)) result.push_back(vector<double>{double(end2[0]), double(end2[1])});
        if(result.empty()) return vector<double>{};
        sort(result.begin(),result.end(),[](vector<double>& l, vector<double>& q) -> bool{return l[0] < q[0];});
        return result[0];
    }
    vector<double> getParm(vector<int>& s, vector<int>& e){
        return {double(e[1] - s[1]),double(s[0] - e[0]),double(e[0] * s[1] - s[0] * e[1])};
    }
    template<typename T1, typename T2, typename T3>
    bool isIntersectionPoint(T1 &p, T2 &s, T3 &e){
        double flag = 1e-7;
        double d1 = sqrt((p[0] - s[0]) * (p[0] - s[0]) + (p[1] - s[1]) * (p[1] - s[1]));
        double d2 = sqrt((p[0] - e[0]) * (p[0] - e[0]) + (p[1] - e[1]) * (p[1] - e[1]));
        double d3 = sqrt((s[0] - e[0]) * (s[0] - e[0]) + (s[1] - e[1]) * (s[1] - e[1]));
        return fabs(d1 + d2 - d3) <= flag;
    }
};

在这里插入图片描述

复杂度分析

这段代码实现了一个计算两条线段交点的算法。以下是对主要函数的时间复杂度与空间复杂度的分析:

intersection() 函数:

  • 时间复杂度:O(1),因为这个函数中的所有操作都是常数时间操作,没有循环或递归。
  • 空间复杂度:O(1),函数内部创建了常数个变量和容器,没有使用动态分配内存。

findPointOnTheParallelLine() 函数:

  • 时间复杂度:O(1),这个函数中的所有操作都是常数时间操作,没有循环或递归。
  • 空间复杂度:O(1),函数内部创建了常数个变量和容器,没有使用动态分配内存。

getParm() 函数:

  • 时间复杂度:O(1),这个函数中的所有操作都是常数时间操作,没有循环或递归。
  • 空间复杂度:O(1),函数内部创建了常数个变量和容器,没有使用动态分配内存。

isIntersectionPoint() 函数:

  • 时间复杂度:O(1),这个函数中的所有操作都是常数时间操作,没有循环或递归。
  • 空间复杂度:O(1),函数内部创建了常数个变量和容器,没有使用动态分配内存。

整个算法的时间复杂度和空间复杂度都是 O(1),因为所有函数都具有常数时间和空间复杂度。

总结

这道题确实是一道困难题,但也是一道十分有乐趣的题。因为它很巧妙的把数学的几何知识融入到了算法题中,让我见识到了数学与算法的结合。

  • 最后的最后,如果你觉得我的这篇文章写的不错的话,请给我一个赞与收藏,关注我,我会继续给大家带来更多更优质的干货内容

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/460593.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

为什么软件测试外包公司更受软件企业欢迎?软件测试报告需要多少钱?

劳动派遣或劳务派遣的用工模式古已有之&#xff0c;是人力资源销售市场不可避免的态势。软件测试顺应时代开展检测业务外包这一行业细分领域&#xff0c;越来越多软件外包公司尤其是小微型企业慢慢意识到了软件测试业务外包通常能够持续减少企业的各种成本费&#xff0c;使企业…

video 视屏播放器详细控制

介绍 canplay、play、pause是video提供的API&#xff0c;在视频加载完成后需要设置视频的总时长duration也是来源于自身的API但是需要格式化时间 设置当前播放时间通过自身API&#xff0c;currentTime function playing(){#id.innerHTMl 格式化时间函数(video.currentTime)…

《用于估计血压变化的光电体积描记图和心电图的特征》阅读笔记

目录 一、摘要 二、十大问题 Q1论文试图解决什么问题&#xff1f; Q2这是否是一个新的问题&#xff1f; Q3这篇文章要验证一个什么科学假设&#xff1f; Q4有哪些相关研究&#xff1f;如何归类&#xff1f;谁是这一课题在领域内值得关注的研究员&#xff1f; Q5论文中提…

maven简单使用

实验课的作业用一大堆框架/库&#xff0c;统统要用maven管理。 头一次用&#xff0c;真痛苦。 所幸得以解决&#xff0c;maven真香&#xff5e; 一步一步来。 1. maven 不是java人&#xff0c;只能说说粗浅的理解了。 简单来说&#xff0c;maven是一个管理项目的工具&…

自定义编码生成

自定义自增编码规则生成 需求场景开发需求需求实现其它 在项目中遇到的需求&#xff0c;这里记录下实现。以下仅供参考&#xff0c;代码有所删减&#xff0c;但核心代码在需求场景 1.需要多个编号规则&#xff0c;不同的场景使用的编码规则不同。 2.编码需要可自定义灵活选择配…

ts定义对象类型Record<string, any>;和object、Object的区别

Record Record 是 TS 内置的一个高级类型&#xff0c;是通过映射类型的语法来生成索引类型的&#xff1a; type Record<K extends string | number | symbol, T> { [P in K]: T; } 比如传入 a | b 作为 key&#xff0c;1 作为 value&#xff0c;就可以生成这样索引类…

Linux安装tomcat以及 对tomcat服务的操作

目录 1、使用FinalShell自带的上传工具将jdk的二进制发布包上传到Linux 2、解压安装包&#xff08;解压到指定位置&#xff0c;-C后面指定位置即可&#xff09; 3、进入Tomcat的bin目录去启动服务&#xff08;两种方式&#xff09; 4、验证Tomcat启动是否成功 方式一&#…

你如何看待,“国内ChatGPT还没成熟,但ChatGPT的付费模式已经成熟了?”

作者&#xff1a;小傅哥 博客&#xff1a;https://bugstack.cn 沉淀、分享、成长&#xff0c;让自己和他人都能有所收获&#xff01;&#x1f604; 说来奇怪&#x1f914;&#xff0c;我们从0到1的事往往较少&#xff0c;但从1到100的嫁衣神功却很多也很快。就像 ChatGPT 还没有…

自有品牌与新兴渠道双轮驱动,丽人丽妆提速起航

2023年4月12日&#xff0c;上海市电子商务行业协会评选出上海市数字商务优秀企业&#xff0c;丽人丽妆凭借在数智化营销领域的专业能力&#xff0c;荣获“上海市数字商务优秀企业”称号。 此次获奖&#xff0c;也反映了丽人丽妆以科技赋能企业高效运营&#xff0c;已经取得突出…

Vue3技术8之Fragment、Suspense、Vue3中其他的改变

Vue3技术8 FragmentTeleport弹窗案例目录结构App.vueChild.vueSon.vueDialog.vue 总结 Suspense普通写法App.vueChild.vue 使用suspense之后App.vueChild.vue 不再自己调整网络低速Child.vueApp.vue 总结补充setup的一个知识点Suspense总结 Vue3中其他的改变全局API的转移其他改…

62 openEuler 22.03-LTS 搭建MySQL数据库服务器-管理数据库

文章目录 62 openEuler 22.03-LTS 搭建MySQL数据库服务器-管理数据库62.1 创建数据库示例 62.2 查看数据库示例 62.3 选择数据库示例 62.4 删除数据库示例 62.5 备份数据库示例 62.6 恢复数据库示例 62 openEuler 22.03-LTS 搭建MySQL数据库服务器-管理数据库 62.1 创建数据库…

一个全端通用的折叠面板,个性定制支持复杂表格

使用uniapp框架全端通用的折叠面板&#xff0c;下载地址见文末 介绍 这是一个全端通用的折叠面板组件&#xff0c;可以折叠/展开的内容区域&#xff0c;支持复杂的表格形式展示&#xff0c;简单的设置即可实现&#xff0c;节省效率必备。 插件含全部源码&#xff0c;可以给您…

Java初学篇——Java语言的发展,特性,基本配置

目录 ps&#xff1a; java的发展 java的特性 java技术体系平台 java的跨平台性 JDK 介绍 安装 Java程序的编译和运行 流程 程序基本框架 一些小知识 注释 常用的转义字符 需要注意的小问题 ps&#xff1a; java算是我第二门系统学习的语言&#xff0c;同时也是我以…

【神经网络】tensorflow实验7--回归问题

1. 实验目的 ①掌握一元线性回归模型的实现方法 ②掌握多元线性回归模型的实现方法 ③掌握三维数据可视化方法 2. 实验内容 ①使用TensorFlow建立一元线性回归模型&#xff0c;使用商品房销售数据训练模型&#xff0c;并使用训练好的模型预测房价 ②使用TensorFlow建立多元线…

SpringBoot 集成 FastDFS(附安装教程)

1、FastDFS 简介 FastDFS是用 c 语言编写的一款开源的分布式文件系统&#xff0c;它对文件进行管理&#xff0c;功能包括&#xff1a;文件存储、文件同步、文件访问&#xff08;文件上传、文件下载&#xff09;等&#xff0c;解决了大容量存储和负载均衡的问题。特别适合以文件…

Bindiff工具使用-[GDOUCTF 2023]L!s!

目录 题目&#xff1a; 学到的点&#xff1a; 题目&#xff1a; 打了GDOUCTF的比赛&#xff08;被暴打了hhh)&#xff0c;学到很多新东西,这里总结一下 Diff的文件是ida数据库文件&#xff0c;选择i64或者idb文件进行Diff 打开附件&#xff0c;有两个文件&#xff0c;一个…

【Git 入门教程】第二节、Git基础使用

Git是一个分布式版本控制系统&#xff0c;它可以帮助开发者更好地管理和维护代码。在本文中&#xff0c;我们将介绍Git的最基本操作&#xff0c;如安装Git、初始化仓库、添加文件、提交代码、查看记录等。 一、Git安装 1.下载 要使用Git&#xff0c;首先需要在计算机上安装G…

JavaWeb开发 —— 登录认证校验和异常处理

在 JavaWeb开发 —— SpringBootWeb综合案例 中我们通过实例部门管理以及员工管理中的详细操作。而这篇文章我们将会通过综合实例学习登录认证、登录校验以及异常处理的了解和掌握。 目录 一、基本登录功能 二、登录校验 1. 会话技术 1.1 客户端会话跟踪技术Cookie 1.2 …

iview render函数(vue render函数)

iview 的render函数就是vue的render函数&#xff0c;iview常用在表格里面自定义内容&#xff0c;下面来看render函数常用的配置&#xff1a; 1、 h是createdElement的简写&#xff0c;有3个参数&#xff1a; 语法&#xff1a;render:(h,params)>{} render:(h,params) >…

mulesoft MCIA 破釜沉舟备考 2023.04.25.24(易错题)

mulesoft MCIA 破釜沉舟备考 2023.04.25.24(易错题) 1. An insurance company is using a CIoudHub runtime plane.2. A mule application must periodically process a large dataset which varies from 6 GB lo 8 GB from a back-end database and write transform data lo…