2025届视觉算法开发工程师面试题目汇总

饭宝  论坛元老 | 2025-4-14 05:12:38 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 1651|帖子 1651|积分 4953

1. 数据布局

1.1 时间复杂度

以下述代码为例:
  1. int n = 100;
  2. for (int i = 0; i < n; i++)
  3. {
  4.         cout<<"test01"<<endl;
  5. }
复制代码
为了方便讨论,这里我们把每一条语句的实行时间都看做是一样的,记为一个时间单元。
这里的,红色框皆为运行1次的,蓝色框运行n+1次,橙色框运行n次,总共泯灭了3n+3个时间单元。可以看出,程序斲丧的时间和这里的n呈现出线性关系。

那么,运行时间的函数可以表示为:T(n) = 3n + 3,其中的n被我们称为题目的规模,着实就是你处理题目的大小。
我们通常会对函数进行简化,使其仍然能够表示原有的函数趋势特性。
我们一般只关心随着题目规模n趋于无穷时,函数中对函数结果影响最大的项,也就是最高次项,简化后,T(n) = 3n + 3 ~ f(n) = n。
时间复杂度可以表示某个算法的运行时间的趋势,大致地度量算法效率的优劣。
总结:
计算时间复杂度的流程:
1、得出运行时间的函数;
2、对函数进行简化。
ps:(1)用常数1来取代运行时间中全部加法常数;
(2)修改后的函数中,只生存最高阶项;
(3)如果最高阶项存在且不是1,则忽略这个项的系数
技巧:
一般,只要看看最内层的语句实行次数的规律就行了,这个内层打印语句随着题目规模n的增加会呈线性增加,直接就可以判定复杂度为O(n)。
  1. for (int i = 0; i < n; i++)
  2. {
  3.         for (int j = 0; j < n; j++)
  4.                 {
  5.                         cout<<"test01"<<endl;  // 最内层语句
  6.                 }
  7. }
复制代码
2. 算法题目

2.1 两数之和

  1. // 暴力枚举法
  2. class Solution {
  3. public:
  4.     vector<int> twoSum(vector<int>& nums, int target) {
  5.         int n = nums.size();
  6.         for (int i = 0; i < n; ++i) {
  7.             for (int j = i + 1; j < n; ++j) {
  8.                 if (nums[i] + nums[j] == target) {
  9.                     return {i, j};
  10.                 }
  11.             }
  12.         }
  13.         return {};
  14.     }
  15. };
复制代码
2.2 递归求二叉树的深度

  1. #include <iostream>
  2. using namespace std;
  3. // 定义二叉树节点结构体
  4. struct TreeNode {
  5.     int val;             // 节点值
  6.     TreeNode *left;      // 左子节点指针
  7.     TreeNode *right;     // 右子节点指针
  8.     TreeNode(int x) : val(x), left(NULL), right(NULL) {}  // 构造函数初始化
  9. };
  10. // 递归计算二叉树的最大深度
  11. int maxDepth(TreeNode* root) {
  12.     // 如果节点为空,深度为 0
  13.     if (root == NULL)
  14.         return 0;
  15.     // 递归求左子树和右子树的深度
  16.     int leftDepth = maxDepth(root->left);
  17.     int rightDepth = maxDepth(root->right);
  18.     // 当前节点的深度等于左右子树深度的较大值加 1
  19.     return max(leftDepth, rightDepth) + 1;
  20. }
  21. int main() {
  22.     // 创建一个简单的二叉树
  23.     /*
  24.            1
  25.           / \
  26.          2   3
  27.         / \
  28.        4   5
  29.     */
  30.     TreeNode* root = new TreeNode(1);
  31.     root->left = new TreeNode(2);
  32.     root->right = new TreeNode(3);
  33.     root->left->left = new TreeNode(4);
  34.     root->left->right = new TreeNode(5);
  35.     // 计算二叉树的最大深度
  36.     cout << "The maximum depth of the binary tree is: " << maxDepth(root) << endl;
  37.     return 0;
  38. }
复制代码
2.3 一个由0和1构成数组中,计算出这里面一连1的最大数

  1. #include <iostream>
  2. #include <vector>
  3. using namespace std;
  4. int findMaxConsecutiveOnes(vector<int>& nums) {
  5.     int maxCount = 0;    // 最大连续1的数量
  6.     int currentCount = 0;  // 当前连续1的数量
  7.     for (int i = 0; i < nums.size(); i++) {
  8.         if (nums[i] == 1) {
  9.             currentCount++;    // 如果当前数字是1,增加连续1的数量
  10.         } else {
  11.             maxCount = max(maxCount, currentCount);  // 遇到0时更新最大连续1的数量
  12.             currentCount = 0;  // 重置当前连续1的数量
  13.         }
  14.     }
  15.     // 可能数组最后的元素是1,循环结束时没有机会更新最大值
  16.     maxCount = max(maxCount, currentCount);
  17.     return maxCount;
  18. }
  19. int main() {
  20.     vector<int> nums = {1, 1, 0, 1, 1, 1};  // 示例数组
  21.     int result = findMaxConsecutiveOnes(nums);
  22.     cout << "The maximum number of consecutive 1s is: " << result << endl;
  23.     return 0;
  24. }
复制代码
3. C++、Python、Cuda题目

3.1 C++

3.1.1 智能指针

智能指针是 C++ 中用于主动管理动态分配内存的一种对象,重要用于解决传统指针在内存管理方面的不敷。它们能够在对象不再须要时主动开释内存,从而减少内存走漏和悬挂指针等题目 。
内存走漏:
程序在运行时动态分配了内存(通常通过 new 或 malloc),但是没有在使用完毕后开释这部分内存(使用 delete 或 free)。这导致程序占用的内存渐渐增加,最终大概耗尽可用内存,导致性能降落或程序崩溃。
悬挂指针:
一个指针指向了已经被开释或未界说的内存地址。
3.1.1.1 std::unique_ptr独占指针

特性:
1.表示对动态分配对象的唯一全部权。
2.不答应复制,但可以转移全部权(使用 std::move)。
3.在 unique_ptr 超出作用域时,主动开释所指向的内存。
  1. #include <iostream>
  2. #include <memory>
  3. void example() {
  4.     std::unique_ptr<int> ptr1(new int(10)); // 动态分配一个整数
  5.     std::cout << *ptr1 << std::endl; // 输出 10
  6.    
  7.     // std::unique_ptr<int> ptr2 = ptr1; // 错误,不能复制
  8.     std::unique_ptr<int> ptr2 = std::move(ptr1); // 转移所有权
  9.     std::cout << *ptr2 << std::endl; // 输出 10
  10.     // ptr1 现在为空
  11. }
复制代码
3.1.1.2 std::shared_ptr共享指针

特性:
1.表示对动态分配对象的共享全部权。
2.答应多个 shared_ptr 指向同一个对象,使用引用计数来管理内存。
3.当末了一个指向该对象的 shared_ptr 被烧毁时,内存将被开释。
  1. #include <iostream>
  2. #include <memory>
  3. void example() {
  4.     std::shared_ptr<int> ptr1(new int(20)); // 动态分配一个整数
  5.     std::cout << *ptr1 << std::endl; // 输出 20
  6.    
  7.     std::shared_ptr<int> ptr2 = ptr1; // 共享所有权
  8.     std::cout << *ptr2 << std::endl; // 输出 20
  9.     std::cout << ptr1.use_count() << std::endl; // 输出 2,表示有两个指针共享同一对象
  10. }
复制代码
3.1.1.3 std::weak_ptr弱指针

特性:
1.重要用于冲破 shared_ptr 的循环引用题目。
2.不控制对象的生命周期,不增加引用计数。
3.可以通过 lock() 方法获得一个 shared_ptr。
作用:用来解决shared_ptr相互引用导致的死锁题目。
当两个或多个 std::shared_ptr 相互持有对方的引用时,它们的引用计数不会降到零,导致内存无法被开释,这就是所谓的循环引用或死锁。
  1. #include <iostream>
  2. #include <memory>
  3. class B; // 前向声明
  4. class A {
  5. public:
  6.     std::shared_ptr<B> b_ptr;
  7.     ~A() { std::cout << "A destroyed\n"; }
  8. };
  9. class B {
  10. public:
  11.     std::shared_ptr<A> a_ptr;
  12.     ~B() { std::cout << "B destroyed\n"; }
  13. };
  14. int main() {
  15.     std::shared_ptr<A> a(new A);
  16.     std::shared_ptr<B> b(new B);
  17.     a->b_ptr = b;
  18.     b->a_ptr = a;
  19.     // a 和 b 互相引用,导致无法释放
  20.     return 0;
  21. }
复制代码
用 std::weak_ptr 解决题目
  1. #include <iostream>
  2. #include <memory>
  3. class B; // 前向声明
  4. class A {
  5. public:
  6.     std::shared_ptr<B> b_ptr;
  7.     ~A() { std::cout << "A destroyed\n"; }
  8. };
  9. class B {
  10. public:
  11.     std::weak_ptr<A> a_ptr; // 使用 weak_ptr
  12.     ~B() { std::cout << "B destroyed\n"; }
  13. };
  14. int main() {
  15.     std::shared_ptr<A> a(new A);
  16.     std::shared_ptr<B> b(new B);
  17.     a->b_ptr = b;
  18.     b->a_ptr = a; // 现在使用 weak_ptr
  19.     // a 和 b 之间的引用关系不会阻止内存的释放
  20.     return 0;
  21. }
复制代码
3.1.2 C++中的引用与指针的区别

引用:
相称于给变量起“别名”,在c++内部着实是一个“指针常量”,必须初始化,且一旦初始化后,就不能改变。
指针:
是一个变量,它存储另一个变量的内存地址。通过指针,可以访问和修改指针所指向的变量。
3.1.3 表明C++中的虚函数和如何使用它实现多态

虚函数是在基类中用virtual声明的函数,它答应在派生类中重写。当使用基类指针或引用指向子类时候,程序会根据现实对象的类型(即派生类)来决定调用哪个版本的函数,而不是基类版本。这种举动称为“动态绑定”或“晚绑定”。
3.1.4 表明重载、重写和隐蔽在C++中的区别

1、重载
重载是指在同一个作用域中,可以界说多个同名的函数或运算符,它们的参数列表必须不同(参数的数量或类型不同)。C++答应函数重载以进步代码的可读性和灵活性。编译时多态。
2、重写
重写是指在派生类中重新界说基类中已经存在的虚函数。重写答应子类提供特定的实现,并且实现与基类的函数具有雷同的名称和参数列表。重写用于实现运行时多态性。动态绑定。
3、隐蔽
隐蔽是指在派生类中界说了一个与基类中同名的函数或变量,导致基类中的函数或变量在派生类中不可见。这种环境下,基类的同名成员函数或变量不会被重写,而是被隐蔽。
  1. #include <iostream>
  2. class Base {
  3. public:
  4.     void display() {
  5.         std::cout << "Base class display function." << std::endl;
  6.     }
  7. };
  8. class Derived : public Base {
  9. public:
  10.     void display(int i) { // 隐藏基类的display函数
  11.         std::cout << "Derived class display function with int: " << i << std::endl;
  12.     }
  13. };
  14. int main() {
  15.     Derived d;
  16.     d.display(10); // 调用派生类的display(int)函数
  17.     // d.display(); // 错误: 基类的display()不可见
  18.     d.Base::display(); // 调用基类的display()函数
  19.     return 0;
  20. }
复制代码
3.1.5 表明C++中的构造函数和析构函数

构造函数:
是一种特殊的成员函数,在对象创建时被主动调用。它的重要作用是初始化对象的属性或分配资源。构造函数的名称与类名雷同,并且没有返回类型(包罗void)。
析构函数:
是一种特殊的成员函数,在对象生命周期竣事时被主动调用。它的重要作用是开释对象在构造时分配的资源(如内存、文件句柄等)。析构函数的名称与类名雷同,但在名称前加上~符号,并且没有参数和返回类型。
3.2 Python

3.2.1 python标准库常见的数据类型

答:
int、float、str、bool、list、set、tuple、dict
3.2.2 def func(*args, **kwargs):函数中,*args, **kwargs是什么意思?args和kwargs各是什么类型?

答:
非关键字参数和关键字参数;tuplie、dict。
3.2.3 从一个字符串str变量中找到含有科学计数法的部分

正则表达式就是一种匹配字符串模式的“公式”。通过正则表达式,你可以从一段文字中查找、替换或验证符合特定模式的字符串。
   string = “xy sfas 1e-6 fs!”
  1. import re
  2. string = "xy sfas 1e-6 fs!"
  3. # 使用正则表达式匹配科学计数法
  4. pattern = r'[-+]?\d*\.?\d+([eE][-+]?\d+)?'
  5. matches = re.findall(pattern, string)
  6. # 打印匹配到的科学计数法部分
  7. print(matches)
复制代码
  string = "xsdaaf2e+9as.
  1. import re
  2. string = "xsdaaf2e+9as"
  3. # 使用正则表达式匹配科学计数法
  4. pattern = r'[-+]?\d*\.?\d+[eE][-+]?\d+'
  5. matches = re.findall(pattern, string)
  6. # 打印匹配到的科学计数法部分
  7. print(matches)
复制代码
3.2.4 a,b两个变量在不使用中心变量的环境下如何交换值

  1. a = 5
  2. b = 10
  3. a, b = b, a
  4. print(a)  # 输出: 10
  5. print(b)  # 输出: 5
复制代码
3.2.5 简述一下装饰器是什么,并设计一个统计函数运行时间的装饰器

3.2.6

(1) 界说一个名为car的类,它有两个属性:“color”和“speed”。然后创建一个实例并返回“speed”。
  1. class Car:
  2.     def __init__(self, color, speed):
  3.         self.color = color  # 定义颜色属性
  4.         self._speed = speed  # 使用单下划线标记为“保护”属性
  5.     def get_speed(self):
  6.         return self._speed  # 返回 speed
  7. # 创建 Car 类的实例
  8. my_car = Car(color="red", speed=120)
  9. # 返回 speed
  10. car_speed = my_car.get_speed()
  11. print(f"The speed of the car is: {car_speed} km/h")
复制代码
(2) 如果我想要访问speed,又想让用户不能修改,怎么实现?(编程形式回答)
  1. class Car:
  2.     def __init__(self, color, speed):
  3.         self.color = color  # 定义颜色属性
  4.         self._speed = speed  # 使用单下划线标记为“保护”属性
  5.     def get_speed(self):
  6.         return self._speed  # 返回 speed
  7. # 创建 Car 类的实例
  8. my_car = Car(color="red", speed=120)
  9. # 访问 speed
  10. car_speed = my_car.get_speed()
  11. print(f"The speed of the car is: {car_speed} km/h")
  12. # 尝试修改 speed(这将不起作用)
  13. try:
  14.     my_car._speed = 150  # 尝试直接修改 speed
  15. except AttributeError as e:
  16.     print(f"Error: {e}")
  17. # 查看 speed 仍然是原来的值
  18. print(f"Speed after attempting modification: {my_car.get_speed()} km/h")
复制代码
3.3 Cuda

3.3.1 描述GPU的内存条理布局

GPU的内存条理布局由几个重要部分构成:
1.全局内存(Global Memory):全部线程都可以访问的大容量存储空间,但访问延迟最高。
2.共享内存(Shared Memory):在同一个线程块(Block)内的线程间共享的低延迟内存。
3.寄存器(Registers):每个线程独有的最快速的存储空间。
4.常量和纹理内存(Constant and Texture Memory):缓存,用于存储频繁访问的数据,可以进步某些类型数据的访问效率。
3.3.2 如安在CUDA中管理内存(分配、开释、数据传输)?

在CUDA中,内存管理涉及在GPU装备的全局内存中分配和开释内存,以及在主机(CPU)和装备(GPU)之间传输数据:
1.分配内存:使用cudaMalloc()函数在GPU上分配内存。
2.开释内存:使用cudaFree()函数开释之前分配的内存。
3.数据传输:使用cudaMemcpy()函数在主机和装备之间复制数据。
3.3.3 什么是核函数(Kernel)?如何界说和调用?

核函数是在CUDA中实行的特殊函数,可以在GPU上并行实行多个线程。核函数通过__global__修饰符界说,并且只能从主机代码调用。
  1. __global__ void kernelName(参数列表) {
  2.     // 核函数代码
  3. }
复制代码
调用核函数时,须要指定实行设置,包罗线程块的数量和每个线程块中的线程数量:
  1. kernelName<<<numBlocks, threadsPerBlock>>>(参数);
复制代码
3.3.4 表明CUDA线程的条理布局

CUDA的线程组织为三级条理布局:
1.网格(Grid):整个核函数的线程块集合。
2.线程块(Block):一组可以协作的线程,共享同一块共享内存。
3.线程(Thread):实行核函数的最小单元。
4. 视觉相关题目

4.1 如何计算一组数据的信息熵?


假设你有一个数据集,包含 4 种种别,分别为 A、B、C 和 D,且它们的出现频率如下:
A 出现了 4 次;B 出现了 2 次;C 出现了 1 次;D 出现了 1 次。
P(A)= 4 / 8 = 0.5;P(B)= 2 / 8 = 0.25;P©= 1 / 8 = 0.125;P(D)= 2 / 8 = 0.125;

4.2 什么是过拟合和欠拟合?

过拟合:是指模子在练习数据上表现非常好,但在测试数据(即未见过的数据)上表现很差的环境。这种现象通常发生在模子过于复杂,能够“记住”练习数据中的噪声或特征,从而对练习数据做出过分拟合。
解决方法:
1、使用更多的练习数据。
2、增加正则化(如L1或L2正则化)以限制模子的复杂度。
3、采用较小的模子,减少参数数量,避免模子过于复杂。
欠拟合:是指模子在练习数据和测试数据上都表现不佳的环境。这意味着模子过于简朴,无法有效学习练习数据中的规律,乃至练习数据上的偏差也很大。
解决方法:
1、使用更复杂的模子(增加参数或使用更复杂的算法)。
2、增加特征维度或进行更好的特征提取。
3、增加练习时间,让模子有更多机会学习数据中的规律。
4.3 什么是模子的偏差和方差?

偏差:
权衡的是模子猜测值与真实值之间的差距,是一种系统性的偏差,通常与模子的假设有关。偏差表示模子的拟合能力,如果偏差过高,说明模子假设过于简朴,无法正确地表达数据的内在模式,导致欠拟合。
方差:
方差:权衡的是模子对练习数据的敏感性,即当使用不同的练习数据集时,模子猜测结果的波动水平。
高方差:模子过于依靠练习数据,练习时能够很好地拟合练习数据中的细节(乃至是噪声),但当应用于新的数据时,表现大概会显着降落。这样的模子通常存在过拟合题目。
4.4 正确度(Accuracy)、精确率(Precision)、召回率(Recall) 和 F1值






4.5 计算bbox的iou

  1. def calculate_iou(bbox1, bbox2):
  2.     x1_min, y1_min, x1_max, y1_max = bbox1
  3.     x2_min, y2_min, x2_max, y2_max = bbox2
  4.    
  5.     # 计算交集
  6.     x_inter_min = max(x1_min, x2_min)
  7.     y_inter_min = max(y1_min, y2_min)
  8.     x_inter_max = min(x1_max, x2_max)
  9.     y_inter_max = min(y1_max, y2_max)
  10.    
  11.     # 交集的面积
  12.     inter_area = max(0, x_inter_max - x_inter_min) * max(0, y_inter_max - y_inter_min)
  13.    
  14.     # 各自的面积
  15.     area1 = (x1_max - x1_min) * (y1_max - y1_min)
  16.     area2 = (x2_max - x2_min) * (y2_max - y2_min)
  17.    
  18.     # 并集的面积
  19.     union_area = area1 + area2 - inter_area
  20.    
  21.     # IoU
  22.     iou = inter_area / union_area if union_area > 0 else 0
  23.     return iou
  24. # 示例
  25. bbox1 = [0, 0, 2, 2]
  26. bbox2 = [1, 1, 3, 3]
  27. iou = calculate_iou(bbox1, bbox2)
  28. print("IoU:", iou)
复制代码
4.6 在使用pytorch练习模子时,我只有32G的RAM, 如何练习512GB的数据呢?

将数据分为多个小批次(mini-batches),每次只加载一个批次的数据到内存中进行练习。PyTorch 的 DataLoader 可以轻松实现这一点。
  1. from torch.utils.data import DataLoader, Dataset
  2. class MyDataset(Dataset):
  3.     def __init__(self, data_file):
  4.         # 读取数据文件(例如,使用 h5py, pandas 等)
  5.         self.data = ...  # 数据读取逻辑
  6.     def __len__(self):
  7.         return len(self.data)
  8.     def __getitem__(self, idx):
  9.         # 返回单个数据样本
  10.         return self.data[idx]
  11. # 创建数据集和数据加载器
  12. dataset = MyDataset("path/to/your/data")
  13. data_loader = DataLoader(dataset, batch_size=32, shuffle=True)
  14. for batch in data_loader:
  15.     # 进行模型训练
  16.     pass
复制代码
4.7 Pytorch和tensorflow计算图区别

Pytorch是动态计算图,tensorflow是静态计算图。
5. 模子部署

5.1 使用tensorrt10设计一个tensorrt模子推理的包装类

使用tensorrt10设计一个tensorrt模子推理的包装类,包含构造函数和forward函数。要求构造函数输入序列化模子路径,初始化完毕后,推理时模拟pytorch的forward函数结果,输入数据,输出最终结果。
5.2 设计神经网络

如何用pytorch设计一个简朴的神经网络(三层卷积,两层全毗连,激活函数Relu,池化,正则化层), 并生成随机数输入给网络,得出结果。
  1. import torch
  2. import torch.nn as nn
  3. import torch.nn.functional as F
  4. class SimpleCNN(nn.Module):
  5.     def __init__(self):
  6.         super(SimpleCNN, self).__init__()
  7.         
  8.         # 第一层卷积
  9.         self.conv1 = nn.Conv2d(in_channels=1, out_channels=16, kernel_size=3, stride=1, padding=1)  # 输入通道1,输出通道16
  10.         self.bn1 = nn.BatchNorm2d(16)  # Batch Normalization
  11.         self.pool = nn.MaxPool2d(kernel_size=2, stride=2)  # 最大池化
  12.         
  13.         # 第二层卷积
  14.         self.conv2 = nn.Conv2d(in_channels=16, out_channels=32, kernel_size=3, stride=1, padding=1)
  15.         self.bn2 = nn.BatchNorm2d(32)
  16.         
  17.         # 第三层卷积
  18.         self.conv3 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, stride=1, padding=1)
  19.         self.bn3 = nn.BatchNorm2d(64)
  20.         
  21.         # 全连接层
  22.         self.fc1 = nn.Linear(64 * 7 * 7, 128)  # 假设输入图像为28x28,经过池化后为7x7
  23.         self.fc2 = nn.Linear(128, 10)  # 输出10个类别
  24.         
  25.     def forward(self, x):
  26.         # 第一层
  27.         x = self.conv1(x)
  28.         x = self.bn1(x)
  29.         x = F.relu(x)
  30.         x = self.pool(x)
  31.         
  32.         # 第二层
  33.         x = self.conv2(x)
  34.         x = self.bn2(x)
  35.         x = F.relu(x)
  36.         x = self.pool(x)
  37.         
  38.         # 第三层
  39.         x = self.conv3(x)
  40.         x = self.bn3(x)
  41.         x = F.relu(x)
  42.         x = self.pool(x)
  43.         # 展平
  44.         x = x.view(-1, 64 * 7 * 7)
  45.         
  46.         # 全连接层
  47.         x = self.fc1(x)
  48.         x = F.relu(x)
  49.         x = self.fc2(x)
  50.         
  51.         return x
复制代码
6. AI题目

1、是否独立部署GPT?

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

饭宝

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表