ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

小鹏自动驾驶中心笔试解析:从C++到路径规划的工程能力考察

小鹏自动驾驶中心笔试解析:从C++到路径规划的工程能力考察 提到小鹏汽车2019春招自动驾驶中心研发笔试题很多准备算法岗的同学第一反应是去找原题、背答案。但以我这几年做自动驾驶相关研发和带人的经验看这份笔试题更像是一张“工程能力筛网”它不指望你把所有题目都答对而是通过几类典型题目快速判断你有没有在真实系统里写过代码、调过数据、跑过仿真。换句话说笔试淘汰的往往不是技术最差的而是那些只刷题、没碰过实车链路的人。这篇内容我会按卷面结构和核心考点拆开讲结合我实际做题和复盘的经验把C、算法、ROS、感知、路径规划这几块的高频考法、常见误区、以及答题时的思考路径全部捋一遍。不管你是准备投自动驾驶算法岗还是已经在做相关方向这份解析都能帮你校准复习方向。1. 春招笔试到底在筛什么考点布局与思考主线1.1 卷面结构看似“八股”实则在测工程底线先说整体感受。小鹏自动驾驶中心这份笔试并不是单一方向的卷子而是把C基础、算法数据结构、ROS系统、机器学习和一点路径规划基础混合在一起。时间一般是90到120分钟题量不大但每题都需要动笔写不是纯选择填空。多数选择题会围绕几个固定主题展开智能指针和内存管理、STL容器的底层原理、ROS话题通信机制、相机和雷达标定的基础概念。编程题通常有两到三道一道偏算法图搜索、动态规划一道偏工程写一个线程安全类、实现一个回调队列。偶尔会有一道综合设计题比如“给出传感器配置设计一个高速NOA场景下的感知和规划流程”。这种题没有标准答案考的是思路是否完整。我见过很多准备很充分的人在第一轮选择题上栽跟头原因不是不会而是太“偏算法”了。比如sizeof一个空类、shared_ptr的循环引用、std::move之后原对象的状态这些在刷LeetCode时基本不会碰到但在自动驾驶C代码库里天天都能遇到。所以这张卷子其实在一开始就划了一条底线你要能在真实工程里写代码而不是只会做OJ题。1.2 小鹏自动驾驶中心的技术栈信号ROS、C与数据闭环从题目分布里可以反向读出团队的研发结构。C和算法占到一半以上说明这个团队的核心系统是C为主不是Python原型堆叠。ROS是基础设施说明当时采用的是模块化分布式架构传感器驱动、感知、规划、控制都由不同节点组成通过话题通信完成数据流转。这类题目的背后其实是一个很直接的需求招进来的人要能直接上手写节点、改算法、跑bag包而不是需要先补半年的工程基础。所以备考时不要只刷C语法题还要把ROS的基本机制、TF坐标变换、时间同步这些概念吃透。很多同学觉得ROS只是工具笔试不会考太深但实际经验告诉我这个话题在笔试面试里出现的频率非常高而且往往不是简单背诵而是让分析具体场景下的数据延迟和坐标变换问题。另外小鹏本身是量产导向的智能电动车公司所以笔试里偶尔会出现一些和“数据闭环”“仿真测试”相关的情境题。比如怎么给corner case构造测试样本怎么衡量感知模型的性能变化这些都指向量产落地而不是纯科研。2. 算法与C基础手撕的不是代码是工程素养2.1 高频题型容器、智能指针、并发、单例先说C部分。几乎每份自动驾驶算法岗笔试题都会考智能指针这里尤其爱考shared_ptr和weak_ptr的配合。典型问法是两个类互相持有shared_ptr会导致什么答案是内存泄漏因为引用计数永远不会归零。解决办法是把其中一个方向的持有改为weak_ptr。你以为这题就结束了但稍微进阶一点的题目会问循环引用在什么场景下出现比如观察者模式里subject和observer互相持有时。容器部分要关注map和unordered_map的底层结构、查找和插入时间复杂度、什么时候该用哪种。自动驾驶里经常需要维护一个“对象ID到目标状态”的映射比如跟踪模块里每个ID对应一系列历史状态这时用unordered_map以ID为key就很合适。如果题目要求按时间顺序遍历那就要用map或者按时间排序的vector底层是红黑树有序但插入稍慢。笔试常考的还有vector扩容机制为什么push_back均摊O(1)怎么用reserve避免频繁搬移。并发也是高频点。std::thread怎么用、mutex锁粒度怎么控制、条件变量怎么避免虚假唤醒。手写一个线程安全队列出现在编程题里的概率不低我在下面会专门讲一个更常见的变体。2.2 典型题示例线程安全单例的实现与失败点分析单例模式是笔试和面试都极爱出的题但自动驾驶岗往往不只考设计模式还结合多线程。下面这种写法是很多刚接触并发编程的同学会交出的版本class Singleton { public: static Singleton* getInstance() { if (instance_ nullptr) { instance_ new Singleton(); } return instance_; } private: static Singleton* instance_; };这段代码在多线程下有数据竞争两个线程可能同时进入if判断导致new两次甚至内存泄漏。这是笔试里常见的“找问题”型题目。正确写法优先考虑C11以后的Meyers Singletonclass Singleton { public: static Singleton getInstance() { static Singleton instance; return instance; } Singleton(const Singleton) delete; Singleton operator(const Singleton) delete; private: Singleton() {} };局部静态变量在C11之后保证线程安全的初始化代码简洁且没有锁开销。如果面试官问加锁方案也可以写std::call_once版本的实现。实际自动驾驶系统里单例多数用于配置类、日志管理器这类全局唯一资源。写这类题目时我建议在答题末尾补一句“为什么用局部静态而不是加锁”这样能体现你对现代C特性的理解深度。2.3 算法题地图搜索与动态规划在自动驾驶题里的变形算法编程题一般不会难到竞赛级别但会把场景包装在自动驾驶背景里让你不能一眼看出来是哪个模板题。最常见的变形是二维网格地图搜索。给定一个0/1网格0代表可通行1代表障碍求两点之间最短路径长度。这就是BFS模板题但如果你答“单源最短路用Dijkstra”本身也没有错只是在网格权重相同的情况下BFS复杂度更低。答题时可以先把BFS实现写出来然后补充说明如果地图上有不同代价区域就要切换成Dijkstra。这样你会显得有场景意识和算法敏感度。另一个常见题型是和路径规划相关的动态规划。比如“自动驾驶车辆在一段道路上需要依次通过n个目标点每两个目标点之间的行驶代价已知求最小总代价”。这就是朴素的DP问题也可以用Floyd或状态压缩DP优化。我在实际批改里见过很多同学一看到“路径”就写图搜索但题目本质是决策最优化需要先建立状态转移方程。我的建议是拿到题先别急着编码花两分钟在草稿上写出状态定义和转移方程错误率会明显下降。还有一类题是手写排序或查找的变种比如在有序数组里找目标值插入位置。本来用二分就能解决但有些同学因为太熟甚至忘了检查边界条件。笔试评分时会跑多个测试用例边界case没过就会扣分。3. ROS与系统基础能不能把多传感器玩明白3.1 话题通信 vs 服务 vs 动作考的是对实时性的理解ROS在自动驾驶岗位的笔试里会被考到这个趋势直到现在我仍然觉得是被低估的因为很多新人把ROS当作“启动器”只在launch文件里启动节点从不深究底层通信机制。话题通信是单向数据流适合传感器数据、状态发布这类高频数据一个发布者对应多个订阅者。这里高频考点是话题的数据是实时到达还是缓存如果订阅者处理跟不上会不会导致丢帧实际中你需要了解队列深度设置消息队列满了以后是丢弃旧数据还是丢弃新数据。在感知或规划链路里数据延迟比丢帧更可怕因为一旦数据乱序时间戳对齐就出问题。服务通信是请求-响应型适合一次性调用比如查询地图某区域的可通行性。动作机制适合需要持续反馈的任务比如“规划一条去停车位的轨迹并持续反馈执行进度”。笔试题喜欢给出一个场景让判断该用哪种通信机制。我给的判断标准很简单如果是持续流式数据用topic如果是轮询/查询用service如果是一个可中断的长任务用action。3.2 坐标变换与时间同步雷达和相机对齐为什么是高频考点坐标变换几乎是必考题。多数场景是车辆底盘坐标系base_link、世界坐标系map、传感器坐标系摄像头camera_link、激光雷达lidar_link之间的变换关系。题目可能给出一组TF树信息让你求某个点在激光雷达坐标系下的坐标转换到某相机坐标系下走过的变换链路。这里最容易出错的地方是变换方向。ROS的TF树里TF表示的是子坐标系在父坐标系下的位姿而不是坐标点本身的变换。很多同学习惯性地“从base到camera”直接用tf_lookup_transform但没有核对坐标点是从哪个坐标系出发的导致结果偏了。我的经验是动手前先在草稿纸上把坐标变换链路画出来严格写成T1 * T2 * P的形式看到矩阵符号至少能避免方向错误。时间同步也是高频概念题。雷达和相机频率往往不同雷达10Hz、相机30Hz怎么把两者数据配对常见方案是“最近邻时间戳匹配”低频传感器数据到达时去查询最近的一帧高频数据或者反之。复杂一些的会用线性插值在两张图像时间戳之间插值出一帧与雷达时间对齐的数据。笔试可能不给代码只问思路和优缺点。这里重点是说出“同步会引入延迟”这和单纯的数据对齐不一样问答时要体现对实时性的理解。3.3 手写一个最小节点的心得如果编程题里出现“用ROS写一个节点订阅某个话题并计算滑动平均后发布”这题考的是你对ROS工程结构的熟悉度。很多同学明明会写C但一遇到ROS的初始化逻辑就卡住。标准写法大致是这样的#include ros/ros.h #include std_msgs/Float64.h class MovingAverage { public: MovingAverage(ros::NodeHandle nh, int window_size) : window_size_(window_size), sum_(0.0) { sub_ nh.subscribe(raw_data, 10, MovingAverage::callback, this); pub_ nh.advertisestd_msgs::Float64(filtered_data, 10); } void callback(const std_msgs::Float64::ConstPtr msg) { data_.push_back(msg-data); sum_ msg-data; if ((int)data_.size() window_size_) { sum_ - data_.front(); data_.pop_front(); } std_msgs::Float64 out; out.data sum_ / data_.size(); pub_.publish(out); } private: int window_size_; double sum_; std::dequedouble data_; ros::Subscriber sub_; ros::Publisher pub_; }; int main(int argc, char** argv) { ros::init(argc, argv, moving_average_node); ros::NodeHandle nh; MovingAverage ma(nh, 5); ros::spin(); return 0; }这题要注意的点是回调函数里不能做耗时操作否则会阻塞其他回调。如果要发布消息尽量在回调里直接发布不要缓存到外部再定时发布除非你有明确的节流需求。如果时间戳设计得好阅卷人会认为你写过真实节点而不是只背过模板。4. 路径规划与决策从搜索到轨迹评分的完整链路4.1 全局规划与局部规划Dijkstra、A*与RRT如何被问到路径规划在笔试里通常以概念题和综合题的方式出现但考察范围很广从图搜索到采样规划都有涉及。Dijkstra是最经典的最短路算法但自动驾驶场景里使用A更常见因为A借助启发函数能大幅减少搜索空间。笔试可能会问A的启发函数怎么设计才能保证最优性答案是启发函数必须可采纳即估计代价不超过真实代价在栅格地图上常用欧几里得距离或曼哈顿距离。如果题目问到“A在什么情况下退化为Dijkstra”答案就是启发函数恒为0时。RRT系列更适合高维空间和连续空间因为不需要显式建模障碍物。但RRT不是最优的所以才有RRT和Informed RRT。笔试问RRT的概率低于A*但一旦问就是概念性辨析在狭窄通道场景下RRT容易失败为什么因为随机采样极难命中狭窄通道内部。此时可以回答加入“桥测试”或者使用Hybrid A*等方法。4.2 轨迹合理性如何评估碰撞安全、舒适性与效率的三元平衡“自动驾驶路径规划是否合理如何评估”这是热词里最值得展开的一部分。我当时在这类题上吃过亏因为它不是套公式而是要你建立一套评估体系。首先要回答轨迹评估不是一个单指标而是一个多目标优化问题。最基础的三个维度是碰撞安全、舒适性和通行效率。碰撞安全要求整条轨迹上的车辆包络与所有障碍物无交集且至少保留一个安全距离这个距离往往随速度变化高速时要求更大的纵向和前向距离。舒适性通常用加速度、加加速度jerk以及横向加速度来评估过大的jerk会让乘客感觉不适指标阈值一般是纵向jerk不超过2到3 m/s^3横向加速度不超过2 m/s^2。通行效率则可以用到达目标点的时间或平均速度来衡量。笔试里如果给一条多项式曲线轨迹让你分析是否合理很多同学只看到“曲率连续”但没有计算曲率的最大值是否超过车辆动力学限制。这是常见的疏漏。车辆在低速时曲率可以很大但高速时曲率必须很小否则侧向力过大导致失稳。所以答题时建议把评估流程写成先检查碰撞再检查运动学可行性最后检查舒适性指标如果三者都通过则轨迹是合理且可执行的。4.3 Frenet坐标与采样规划笔试中最容易失分的概念题Frenet坐标系在路径规划题里的出现频率相当高。所谓Frenet坐标系是用参考线通常是道路中心线的切线方向为纵向s法线方向为横向d来描述车辆相对于道路的位置。为什么要用Frenet因为道路通常是弯曲的笛卡尔坐标系下很难表达“沿车道走”这个动作而Frenet坐标天然把“在车道内行驶”变成d的范围约束把“前方多远”变成s的范围约束。笔试可能让你解释如何从Cartesian坐标转换到Frenet坐标或者反过来。关键公式并不复杂但如果你没在实际代码里写过很容易被绕进去。我的建议是不用死背公式但要理解s是投影点在参考线上的弧长d是车辆到投影点的有向距离而投影时要用参考线的切线方向做正交分解。采样规划是Frenet坐标系下的常见算法。基本思路是沿参考线采样一组终点状态每个终点状态包含s、d、速度、加速度然后为每个终点生成一条横向和纵向多项式轨迹再根据代价函数选择最优轨迹。笔试考这个场景时往往让你画出或描述代价函数的组成部分。这里要给全与参考线的横向偏差代价、与目标速度的偏差代价、舒适性代价jerk、以及碰撞代价。如果你能额外提到“横向和纵向分开优化再合成轨迹”这一点会显得你真正理解Frenet规划。5. 感知与深度学习模型之外更考工程评测能力5.1 目标检测高频点NMS、IoU、mAP与数据集感知题目一般不会让你从头推导一个模型但会考评估指标和常用策略其中NMS非极大值抑制手写几乎成了必备题。要实现NMS先理解IoU两个框的交集面积除以并集面积用于衡量两个框的重叠程度。NMS的流程是按置信度从高到低排序逐个选中一个框剔除所有与它IoU超过阈值的框。一个简单的NMS实现长这样def nms(boxes, scores, iou_threshold0.5): order scores.argsort()[::-1] keep [] while order.size 0: i order[0] keep.append(i) xx1 np.maximum(boxes[i, 0], boxes[order[1:], 0]) yy1 np.maximum(boxes[i, 1], boxes[order[1:], 1]) xx2 np.minimum(boxes[i, 2], boxes[order[1:], 2]) yy2 np.minimum(boxes[i, 3], boxes[order[1:], 3]) w np.maximum(0.0, xx2 - xx1) h np.maximum(0.0, yy2 - yy1) inter w * h area_i (boxes[i, 2] - boxes[i, 0]) * (boxes[i, 3] - boxes[i, 1]) area_o (boxes[order[1:], 2] - boxes[order[1:], 0]) * (boxes[order[1:], 3] - boxes[order[1:], 1]) iou inter / (area_i area_o - inter 1e-6) inds np.where(iou iou_threshold)[0] order order[inds 1] return keepmAP也是必考概念。mAP是不同类别AP的平均值而AP是precision-recall曲线下面积。笔试里容易混淆的是预测框和GT框怎么匹配通常用IoU阈值判断比如COCO风格用0.5到0.95多个阈值取平均。实际自动驾驶里还会要求按距离分难易度近处的检测要求更严格。关于数据集热词里提到的“自动驾驶数据集”在这里很有用。KITTI是经典起步集nuScenes提供了多传感器标注、雷达和毫米波数据Waymo Open Dataset规模更大、场景更丰富。答到“如果做量产模型单靠公开数据集不够需要自建数据闭环”会是一个很加分的点。5.2 从单目到点云BEV与数据闭环的上下文感知题目如果出综合题往往会从传感器配置出发。小鹏的量产车带多个摄像头、毫米波雷达和超声波雷达这些传感器各有优劣。摄像头纹理信息丰富但缺深度激光雷达测距准但成本高毫米波雷达对速度敏感但分辨率低。笔试题目可能让你设计一个融合方案这里最好能提到“前融合”或“后融合”两种路线。再进一步是BEVBird Eye View感知。近年来的趋势是把多摄像头特征投影到统一的鸟瞰视角下做检测这样感知结果天然在车辆坐标系方便后续规划模块直接使用。如果试卷里提到“多传感器融合”或“鸟瞰视角”你可以解释BEV相比透视视角的好处目标尺度不随距离变化纵向横向距离更直观便于做轨迹预测。数据闭环也是量产自动驾驶里的关键工程问题。简单说就是在路测中发现模型fail的corner case自动或半自动地把这些数据回传经过标注、增强后重训模型再通过回归测试验证效果。笔试如果问“如何评估感知模型更新后是否变好”除了看测试集mAP还要看corner case上的具体性能比如夜间、雨天、逆光场景是否显著改善。这比单纯看整体mAP更能反映量产效果。5.3 自动驾驶测试与数据集为什么“评估”本身就是技术点“自动驾驶测试”这个词可以出现在两个地方一是系统级的道路测试和仿真测试二是模型级的评测。笔试里综合设计题往往会考前者。系统级测试的核心问题是怎么证明一个自动驾驶系统是安全的答案不是“跑了很多路”而是要有目的性的仿真场景库和路测指标。搭建仿真场景时要考虑目标车辆的切入、行人横穿、施工区域、雨雪天气等典型危险场景。笔试让你设计一个仿真场景最好按步骤写场景参数、主车初始状态、交互车辆行为、预期发生的危险情况、观察指标最小TTC、横向距离、加速度。另外“感知测试”里有个点很值得提评估一个障碍物检测模型不能只用AP还要看它的假阴性重灾区在哪里。自动驾驶里漏检行人比误检的代价更大。所以如果你能答出“针对不同类别分别评估并且对行人、骑行者等弱势道路使用者设置更严格的漏检门槛”会显得你有安全导向的工程思维。6. 踩坑实录与备考建议写给准备下一次笔试的你6.1 时间分配的三种失败模式我从身边同学和实际笔试经验里总结出三种典型的翻车模式你可以对照着规避。第一种是在选择题上死磕。有些选择题涉及很细的边界比如某个C标准草案里的细节你在考场里很难快速确认与其耗5分钟不如先标记跳过。一套卷子的分值往往编程题占比更高选择题丢了2分不要紧编程题没写完才是大问题。第二种是编程题一上来就写最优解。笔试时间有限最优解往往需要较长的推导过程万一卡在实现细节上整个题就报废了。我建议先写一个能跑的暴力解法拿到基础分再不断优化。如果还有时间再补充能体现算法能力的优化版本。阅卷人看到你从暴力到优化的演进印象分通常比只看到一个没写完的最优解更高。第三种是综合题只写结论不写过程。比如问“如何评估一条轨迹是否合理”你只写“看碰撞和舒适性”这样拿分很有限。更稳妥的写法是列出所有评估维度每个维度给出具体指标或公式最好再补一个简易流程。哪怕你没有实际跑过也能体现出你的结构化思维。6.2 手撕代码阶段的典型翻车点手撕代码的扣分点往往不在思路而在实现细节。边界条件是最常见的扣分点。比如NMS实现里没有考虑空输入动态规划里数组越界网格搜索里没有标记已访问节点导致死循环。我的自查习惯是写完代码后用笔在几个典型输入上手动走一遍确认没有越界和死循环再提交。内存管理也是扣分点。写C时如果用了new一定要记得delete如果你写的是面向生产环境的代码建议直接用智能指针。笔试虽然没有在线运行但阅卷人看到裸指针和new时会下意识判断你缺少量产代码经验。命名和结构同样是隐性评分项。有些同学写函数名全是a、b、c阅卷人需要花很长时间才能看懂逻辑。我见过一些基础能力不错但代码可读性很差的人最终没有进入下一轮非常可惜。建议函数名用见名知义的词组比如computeIoU、isCollisionFree、generateTrajectory甚至在关键步骤写一行注释都会提升整体印象。6.3 一些长期有效的自学路径如果你现在离笔试还有一段时间除了刷题我建议做几件对长期成长更有效的事。第一自己搭一个ROS仿真环境。不需要真车用Gazebo或者一些开源模拟器配合Rviz就行把一个虚拟激光雷达的数据接到自己的节点里做一个简单的障碍物检测和避障演示。这个过程中你会自然接触TF、时间同步、话题通信和可视化比刷十道理论题都管用。第二至少完整复现一个经典检测模型并跑在KITTI或nuScenes数据集上。不用追求性能超过论文但要跑通训练、验证、测试、可视化完整链路。这样笔试里提到mAP、IoU、NMS这些概念时你脑子里的不只是公式还有实际调参时踩过的坑。第三把“规划合理性评估”做成一个可执行的小项目。选一条高速公路场景用采样或搜索算法生成轨迹再写一个评估函数考虑碰撞、曲率、jerk、效率几个维度。跑完以后你自然会理解为什么笔试里会问那些指标而不只是背下来。最后再分享一个我自己复盘时的小习惯每次笔试完把不会的题按“知识盲区”和“场景盲区”两类记录。知识盲区靠补文档和课本解决场景盲区则需要去跑代码、看开源工程、读别人的实战笔记。自动驾驶这个方向光靠刷题永远追不上工程迭代只有持续在真实系统里折腾才能让笔试和面试变成一次次顺理成章的输出。
返回列表