控制算法工程师面试题目分享【持续更新】

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录


前言

最近在面试,记录一下自己遇到的问题还有整理的答案,希望能够找到份不错的工作。
所有的问题都是看着简历来的,所以简历中的内容一定要十分熟悉且了解。

主要面试方向是自动驾驶控制算法工程师和机器人控制算法工程师。

当你简历能过关的时候,就需要结合简历,把有可能问道的答案写下来,否则面试的时候容易磕磕绊绊,观感很不好。


一、你工作内容中写的机器人的信息流是用ros吗?讲一下整个机器人的信息流?机器人是如何执行的。

详情见我的另外两篇文章:
https://blog.youkuaiyun.com/weixin_48386130/article/details/143479430?spm=1001.2014.3001.5501
https://blog.youkuaiyun.com/weixin_48386130/article/details/143477139?spm=1001.2014.3001.5501

这部分也就包含了整个机器人的工作流,仓储物流机器人都大同小异。

二、项目中写了运用了LQR,那结合项目讲一下LQR,包括输入输出等。

三、每个项目都说出自己的三个工作内容和三个改进的点。

这部分就需要根据自己的实际工作经历和简历上的来了,还是需要提前准备一下,一般不会问超过三个。

四、c++中的多态指的是什么,在实际项目程序中的应用。

我写的熟悉c++,c++八股文肯定少不了,但是这次问是结合项目,问我如何使用。
答案分两部分:
1、八股文:多态指的是同一接口在不同对象上可以表现出不同的行为。
实现方式分为两种:静态多态(编译时多态)和动态多态(运行时多态)。
其中静态多态:函数重载,运算符重载
动态多态:通过虚函数和指针或引用实现。当基类指针或引用指向派生类对象时,可以调用派生类实现的函数,而无需显式地指明调用的是哪个类的实现,这就是多态的核心。
严格意义上来说,只有动态多态算是真正的多态。
2、在项目上的使用:
机器人控制系统中,行走控制、举升控制、避障控制等模块通常需要不同的实现。可以设计一个控制模块的基类 ControlModule,并为不同的控制模块创建子类。
这样一来,主控制系统在运行时,可以动态地选择使用哪个控制模块,而无需关心模块的内部实现。

class ControlModule {
public:
    virtual void control() = 0;
    virtual ~ControlModule() {}
};

class DriveControl : public ControlModule {
public:
    void control() override {
        // 行走控制的具体实现
    }
};

class LiftControl : public ControlModule {
public:
    void control() override {
        // 举升控制的具体实现
    }
};

// 使用多态进行控制
std::vector<std::unique_ptr<ControlModule>> modules;
modules.push_back(std::make_unique<DriveControl>());
modules.push_back(std::make_unique<LiftControl>());

for (auto& module : modules) {
    module->control();  // 动态绑定到不同模块的 control 实现
}

这部分引申一下,还有其他一些c++八股文,也可能会问到在项目中的使用,背八股文的时候记得想一下。

五、硕士期间使用了NMPC算法,当时为什么选择了这种算法,和普通mpc改进了哪些,有哪些优势。

学校期间不太严格要求控制周期的稳定以及实时性,所以选择nmpc来作为跟踪控制算法。nmpc相比于mpc而言,预测模型没有进行线性化处理,预测精度会相对于mpc高一些(缺点:nmpc非线性求解更加耗时),nmpc由于预测模型是非线性的,在预测模型上做学术研究可拓展性比较大,例如在预测模型上加一些机器学习的算法来提高预测能力。

六、项目中用的s型曲线速度控制策略的输入输出是什么。

s型曲线速度规划策略中输入:当前速度和位置,目标路径,最大速度,最大加速度和最大加速度率。
输出:速度随时间变化的函数,(位置,加速度随时间变化的函数也可以输出)

十、最后实现的控制精度。

纵向正负5mm,横向正负2cm,这个基本符合大部分市面上室内定位的仓储物流机器人的最高精度。

十一、串口是使用can吗,简单说一下它在你所做机器人中的应用。

运动控制:机器人通常有多个电机和驱动器,如差速轮电机、旋转关节电机等。CAN总线可以将多个驱动模块连接到一个控制系统上,实现集中控制。
传感器数据采集:CAN总线经常用于传输多种传感器数据,包括激光雷达、IMU、编码器、超声波传感器等。这些传感器数据会被发送到主控制器,用于路径规划、定位、避障等功能。
状态监测与诊断:CAN总线允许机器人实时监控各个模块的状态,包括电机状态、温度、电流、电压等信息。一旦出现异常,如温度过高、电流异常,CAN总线会立即将信息反馈给主控制器,帮助实现保护或停机处理。
这部分需要看一下can总线和can通讯等。

十二、一道排序算法题。

我没刷题。


十三、最长回文字符串算法题,没有让写代码,就是问了思路,同时还有时间复杂度和空间复杂度。

最长回文字符串,方法有两种:
1、中心扩展法:时间复杂度O(n^2),空间复杂度O(1);
2、动态规划:时间复杂度O(n^2),
空间复杂度O(n^2);

时间复杂度和空间复杂度的计算其实我现在都不太懂,
代码的时间复杂度和空间复杂度的计算在此整理一下:
在 C++ 代码中计算时间复杂度和空间复杂度,可以帮助我们了解代码的效率。以下是一些基本的概念和如何分析代码的复杂度。(gpt粘过来的)
或者参考另外博主的博客:https://blog.youkuaiyun.com/weixin_63866037/article/details/128087397
https://blog.youkuaiyun.com/vpurple_/article/details/126018218

1. 时间复杂度(Time Complexity)

时间复杂度表示算法随着输入规模的增加,执行时间的增长情况。常见的时间复杂度有:

  • 常数时间:(O(1))
  • 对数时间:(O(\log n))
  • 线性时间:(O(n))
  • 线性对数时间:(O(n \log n))
  • 二次时间:(O(n^2))
  • 立方时间:(O(n^3))
  • 指数时间:(O(2^n))

如何计算时间复杂度

1.1 循环结构

循环次数往往决定了时间复杂度。

// 线性时间复杂度 O(n)
for (int i = 0; i < n; ++i) {
    // 常数时间操作
}

// 二次时间复杂度 O(n^2)
for (int i = 0; i < n; ++i) {
    for (int j = 0; j < n; ++j) {
        // 常数时间操作
    }
}
1.2 分支结构

分支语句不会增加时间复杂度,取决于执行次数最多的路径。

if (condition) {
    // O(n)
} else {
    // O(n^2)
}
// 时间复杂度取决于耗时最大分支路径
1.3 递归结构

递归时间复杂度通常使用递归方程求解,例如:

int fibonacci(int n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

此代码的递归方程为 (T(n) = T(n - 1) + T(n - 2) + O(1)),复杂度为 (O(2^n))。

1.4 常见的算法复杂度
  • 排序算法如快速排序:(O(n \log n))
  • 查找算法如二分查找:(O(\log n))
  • 哈希查找:(O(1)) 平均复杂度

2. 空间复杂度(Space Complexity)

空间复杂度表示算法运行过程中所需的额外空间。常见空间复杂度有:

  • 常数空间:(O(1))
  • 线性空间:(O(n))
  • 二次空间:(O(n^2))

如何计算空间复杂度

2.1 基本变量和常量

基本变量和常量不随输入规模变化,通常为 (O(1))。

int a = 0; // O(1)
2.2 数组和容器

如果使用一个长度为 (n) 的数组或容器,其空间复杂度为 (O(n))。

int arr[n]; // O(n)
2.3 递归调用栈

递归深度会影响空间复杂度。深度为 (n) 的递归栈需要 (O(n)) 的空间。

void recursive(int n) {
    if (n == 0) return;
    recursive(n - 1);
}

此递归的空间复杂度为 (O(n))。

综合示例

分析以下代码的时间复杂度和空间复杂度:

void exampleFunction(int n) {
    int sum = 0;               // O(1) 空间
    for (int i = 0; i < n; ++i) {  // O(n) 时间
        sum += i;
    }
    std::vector<int> vec(n);    // O(n) 空间
    for (int i = 0; i < n; ++i) {  // O(n) 时间
        vec[i] = i * i;
    }
}
  • 时间复杂度:第一层循环为 (O(n)),第二层循环也是 (O(n))。因此总的时间复杂度为 (O(n) + O(n) = O(n))。
  • 空间复杂度:常数空间 (O(1)) 和大小为 (n) 的数组,因此总的空间复杂度为 (O(n))。

十四、c++的特性和如何变成二进制代码。

封装继承多态,模板,标准库等等,讲一下,这个没什么多余好说的。
(下面的是gpt粘贴的)
将 C++ 程序转换成二进制代码的过程叫做“编译”,它通常包含以下四个步骤:预处理、编译、汇编 和 链接。

  1. 预处理(Preprocessing)
    首先,编译器会对 C++ 源代码进行预处理。预处理器负责处理预处理指令,如 #include、#define、条件编译指令等。这些指令不会直接转化为二进制代码,而是会通过预处理生成一个无预处理指令的纯 C++ 文件。
  2. 编译(Compilation)
    在编译阶段,编译器将预处理后的 C++ 代码转换为 汇编代码。编译器会分析代码的语法和语义,将每个语句翻译成对应的汇编指令。这些汇编指令是人类可读的低级语言代码,是对 CPU 指令集的抽象表示。可以通过 -S 选项来查看生成的汇编代码。
  3. 汇编(Assembly)
    汇编器(Assembler)会将汇编代码转换成 机器码(即二进制代码),生成一个目标文件(通常是 .o 或 .obj 文件)。机器码是二进制的,由 0 和 1 组成,表示 CPU 可以直接执行的指令。这个目标文件包含了程序的机器代码表示,但还不是最终的可执行文件,因为它还没有链接库函数和其他模块。

在这个步骤之后,example.o 文件已包含了程序中的所有指令的二进制表示,但这只是代码和数据的集合,未形成完整的可执行文件。
4. 链接(Linking)
链接器(Linker)会将多个目标文件和库文件链接在一起,生成最终的可执行文件。链接器的任务是将程序的各个部分连接在一起,并解决外部函数或变量的引用。例如,标准库函数 std::cout 在链接阶段被链接到实际的标准库实现中。

链接后,生成的文件通常是二进制的可执行文件,如在 Linux 中是 ELF 格式(.out 或无后缀),在 Windows 中是 PE 格式(.exe)。
整个过程将 C++ 源代码变成二进制可执行文件:

预处理:展开预处理指令,生成纯 C++ 代码。
编译:将纯 C++ 代码转换成汇编代码。
汇编:将汇编代码转换成机器码,生成目标文件。
链接:将所有目标文件与库链接,生成最终的可执行二进制文件。

十五、路径跟踪的评价指标,用什么方式实现评价的。

这部分我当时答的是运行轨迹和目标路径的偏差。

十六、控制算法频率。

无人驾驶系统中的控制算法频率因模块和任务的不同而不同,各模块典型频率范围如下:

感知模块:10-100 Hz
路径规划:1-10 Hz
运动控制:50-100 Hz
底层执行控制:100-500 Hz
状态估计:100-1000 Hz

十七、git库的上传代码命令等。

git push origin ,这里origin通常是主仓库,是你想推送至的分支名。
Git代码管理工具的一些基本指令需要了解,可以阅读这位博主的文章:
https://blog.youkuaiyun.com/qq_44930306/article/details/126473598

十八、Linux常见命令,查看进程的命令等。

  1. 查看进程的命令
    ps:查看当前系统中运行的进程。

ps aux:显示系统中所有进程的详细信息。
ps -ef:另一种格式,显示所有进程的详细信息。
ps -u <用户名>:查看某个用户的进程。
top:实时监控系统中的进程情况,显示进程的CPU、内存使用情况等(按 q 退出)。

htop:top 的增强版,提供更友好的界面(需要安装)。
pidof <进程名>:查看某个进程的PID。

pgrep <进程名>:根据进程名称查找进程ID,支持模糊匹配。

pgrep -u <用户名>:显示某个用户运行的进程ID。
pstree:以树状结构显示进程,方便查看父子进程关系。

lsof:查看系统中打开的文件及其对应的进程,常用于排查进程对文件的占用情况。

lsof -i:<端口号>:查看使用特定端口的进程。
netstat:显示网络相关的进程信息。

netstat -tulnp:查看所有监听的端口及对应的进程(适用于排查网络连接)。
kill:终止指定PID的进程。

kill :向指定进程发送 TERM 信号,通常会安全地终止进程。
kill -9 :强制终止进程,不经过资源清理。
pkill:通过进程名称终止进程。

pkill <进程名>:终止所有匹配名称的进程。
pkill -u <用户名>:终止某个用户的所有进程。
killall:通过进程名终止进程。

killall <进程名>:杀掉指定名称的所有进程。
2. 系统信息查看命令
uname -a:查看系统的详细信息。
uptime:查看系统的运行时间和负载。
df -h:查看磁盘使用情况。
du -sh <目录>:查看目录的大小。
free -h:查看内存使用情况。
vmstat:查看系统的内存、进程、CPU等信息。
iostat:查看CPU和磁盘I/O统计信息。
3. 文件操作相关命令
ls:列出目录内容。

ls -l:详细列表显示。
ls -a:显示隐藏文件。
cd <目录>:切换目录。

cp <源文件> <目标文件>:复制文件或目录。

mv <源文件> <目标文件>:移动或重命名文件。

rm <文件>:删除文件。

rm -r <目录>:删除目录及其内容(递归删除)。
cat <文件>:查看文件内容。

more <文件> / less <文件>:分页查看文件内容。

head <文件> / tail <文件>:查看文件的头部或尾部内容。

touch <文件>:创建空文件或更新文件的修改时间。

  1. 网络相关命令
    ping <IP或域名>:测试网络连接。

ifconfig:查看和配置网络接口信息(在现代系统中使用 ip a 代替)。

netstat:查看网络连接、路由、端口等信息。

ss:netstat 的替代品,功能更强大。

curl / wget :下载文件或测试网络请求。

  1. 压缩解压相关命令
    tar -cvf <压缩文件名> <目录>:将目录打包成 tar 文件。
    tar -xvf <压缩文件名>:解压 tar 包。
    gzip <文件>:压缩文件生成 .gz 文件。
    gunzip <文件>:解压 .gz 文件。
  2. 用户和权限管理
    chmod:修改文件权限。

chmod 755 <文件>:设置文件权限为 755。
chown <用户>:<组> <文件>:更改文件的所有者和组。

useradd <用户名> / userdel <用户名>:添加或删除用户。

passwd <用户名>:设置用户密码。

十九、激光slam的基本原理,LOAM的基本原理(我简历里有些)。

slam就是拼图,一下是gpt的回答:
激光SLAM(Simultaneous Localization and Mapping,实时定位与地图构建)是一种基于激光雷达传感器进行环境建图和自主定位的技术,广泛用于机器人、无人驾驶汽车和自动化工业中。LOAM(Lidar Odometry and Mapping)是一种经典的激光SLAM算法,能够实现高精度的激光里程计和建图。下面分别介绍激光SLAM的基本原理和LOAM的工作原理。

激光SLAM的基本原理

激光SLAM的核心目标是通过传感器(激光雷达)实时获取环境信息,估计机器人或车辆在环境中的位置,同时在未知环境中构建地图。激光SLAM主要依赖激光雷达数据的空间特征,通过分析多个时刻的点云数据来完成自定位与地图构建。其基本工作流程如下:

  1. 数据采集:使用激光雷达等传感器实时获取环境的距离信息,将环境扫描成三维点云或二维激光扫描数据。

  2. 特征提取:从激光雷达的点云数据中提取特征(如角点、边缘点、平面等),这些特征可以帮助机器人找到不同时间的扫描数据间的对应关系。

  3. 配准算法:使用ICP(Iterative Closest Point)等点云配准算法,计算出两次扫描数据间的相对位姿变化。配准算法的目标是使两个时刻的点云数据尽可能对齐,以估计位姿变化。

  4. 位姿估计(Odometry):通过两次激光扫描的相对位姿变化,计算出机器人当前的运动轨迹,从而实现里程计(Odometry)功能。

  5. 回环检测:识别出机器人经过相同位置的情况,称为回环检测。回环检测可以减少累计误差并优化全局轨迹,有助于提高定位精度。

  6. 图优化:将当前帧与之前的多帧配准,形成图结构。利用图优化算法(如g2o或Ceres)进行全局优化,以消除累积误差。

LOAM的基本原理

LOAM是一种基于激光雷达的SLAM算法,分为激光里程计和地图优化两个模块,用于提高高频率位姿估计的精度和计算效率。LOAM算法的设计目标是保证高频、低延迟的里程计估计,同时确保低频的地图构建具有足够的精度。LOAM算法的关键原理和步骤如下:

  1. 点云数据分割

    • 将激光雷达的扫描数据分为多个扫描线,根据激光束的角度信息,将每一帧的点云划分成扫描线上连续的点。
    • 根据点的曲率和距离判断点的特征类型,选择性地提取角点和边缘点以减小计算量。
  2. 特征提取

    • 计算点云中每个点的曲率,曲率高的点被认为是角点(具有尖锐特征),曲率低的点被认为是平面点。
    • 保留关键特征点用于后续的配准计算,去除无用的点以降低计算量。
  3. 激光里程计(Laser Odometry)

    • 在小范围内将特征点与前一帧的特征点匹配,通过配准计算出帧间的相对位姿变化(旋转和平移)。
    • 采用点到线、点到面的距离约束进行配准优化,以提高位姿估计的精度和鲁棒性。
    • 激光里程计的估计频率通常较高(如10Hz以上),实现实时性。
  4. 低频地图构建(Mapping)

    • 将多帧的点云数据融合构建局部地图,以提供更精确的环境表示。
    • 采用图优化算法对局部地图和全局地图进行优化更新,以消除累积误差。
    • 地图构建模块通常以较低的频率(如1Hz)运行,以便保证计算资源主要用于激光里程计,提供实时的位姿估计。
  5. 地图与回环检测

    • 构建局部地图并逐步更新全局地图,回环检测用于检测机器人经过的相同位置。
    • 一旦检测到回环,通过图优化对整个轨迹进行约束,从而消除累计误差,提高全局精度。

LOAM算法的优势

LOAM的核心优势在于将里程计估计和地图构建分成两个不同的频率进行处理。高频的里程计保证了实时的位姿估计,而低频的地图构建则保证了精度。通过分步优化LOAM可以在保证精度的同时,适应激光雷达的高频数据流。

总结

  • 激光SLAM主要通过激光数据的特征提取、配准和图优化来实现位姿估计与地图构建。
  • LOAM算法将里程计和地图构建分频处理,使用特征点配准计算相对位姿,并优化低频的全局地图,达到高精度和实时性的平衡。

二十、matlab用过哪些,simulink用过的模块,carsim用过的模块,是否有接触过simulink文件转为c文件的功能。

二十一、模块化设计。

二十二、嵌入式开发,烧板子。

二十三、力扣上的两道题:15.三数之和;322.零钱兑换。大概给了40分钟手撕。

二十四、s型曲线的如何保证它的凹凸性?

二十五、s型曲线和贝塞尔约束结合的速度规划,是如何保证效率最高的。

二十六、LQR如何保证横向误差和航向误差的权重。

二十七、为什么使用NMPC,有什么区别。

二十八、乘用车无人驾驶和矿用车还有agv机器人的控制,你认为最大的区别在哪里。

二十九、解释一下最小二乘法。

三十、说一下算法稳定性判据。

三十一、多线程实现。

三十二、python中numpy将两个数组合并。

三十三、继承类型,私有共有等

三十四、vertor中的resize和reverse区别

三十五、纯虚函数

三十六、智能指针的实现和应用

三十七、实际编程过程中使用值传递和引用传递等不同,为什么选择

三十八、实现share——ptr

三十九、讲一下卡尔曼滤波算法

四十、四元数的实部和虚部分别指什么

四十一、map和unordered_map的区别,查找时谁的时间复杂度高,谁的空间复杂度高

map:基于红黑树。元素是有序的,按键值从小到大排列。查找、插入、删除的时间复杂度为 O(logn),因为是树结构。
unordered_map:基于哈希表。元素是无序的,按哈希函数的计算结果存储。平均情况下,查找、插入、删除的时间复杂度为 O(1),因为是哈希表。

四十二、ros.spin()什么意思

用于保持节点的运行状态,确保节点可以持续地接收和处理回调函数。

四十三、ros中的imu和lidar的时间戳同步

ROS 提供了 message_filters 库,可以实现基于时间的多传感器消息同步。
或者外部硬件时间同步。

四十四、讲一下svm和聚类

SVM 的目标是找到一个最优超平面,将不同类别的数据点最大化分开。
支持向量机之后单独出一版整理一下。

四十五、激光雷达如何去除地面点

四十六、智能指针

四十七、const和static

const 表示“不可变”,用于定义常量或防止修改变量的值。
变量一旦被声明为 const,它的值在初始化后不能再被修改。
指针自身为常量(不能改变指向的地址)
避免函数修改参数的值
const 成员函数不能修改类的成员变量。

static 表示“静态”,控制变量的生命周期和作用域。
局部 static 变量只在函数内可见,但它的生命周期贯穿程序运行。
static 全局变量的作用域限制在当前文件内,不能被其他文件访问
static 成员变量属于类,而不是某个对象。所有对象共享一个 static 成员变量。
static 成员函数不依赖对象,可以直接通过类名调用。
不能访问非 static 成员变量。

四十八、const在函数中的不同地方起到的不同作用

四十九、static在类的成员函数前能否实例化

不能

总结

主要是记录和更新问题,答案先不要看我的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

不断学习加努力

俺会努力的,一直免费的!

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

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

打赏作者

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

抵扣说明:

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

余额充值