2025届视觉算法开发工程师面试问题汇总
- 1. 数据结构
- 1.1 时间复杂度
- 2. 算法问题
- 2.1 两数之和
- 2.2 递归求二叉树的深度
- 2.3 一个由0和1组成数组中,计算出这里面连续1的最大数
- 3. C++、Python、Cuda问题
- 3.1 C++
- 3.1.1 智能指针
- 3.1.1.1 std::unique_ptr独占指针
- 3.1.1.2 std::shared_ptr共享指针
- 3.1.1.3 std::weak_ptr弱指针
- 3.1.2 C++中的引用与指针的区别
- 3.1.3 解释C++中的虚函数和如何使用它实现多态
- 3.1.4 解释重载、重写和隐藏在C++中的区别
- 3.1.5 解释C++中的构造函数和析构函数
- 3.2 Python
- 3.2.1 python标准库常见的数据类型
- 3.2.2 def func(*args, **kwargs):函数中,*args, **kwargs是什么意思?args和kwargs各是什么类型?
- 3.2.3 从一个字符串str变量中找到含有科学计数法的部分
- 3.2.4 a,b两个变量在不使用中间变量的情况下如何交换值
- 3.2.5 简述一下装饰器是什么,并设计一个统计函数运行时间的装饰器
- 3.2.6
- 3.3 Cuda
- 3.3.1 描述GPU的内存层次结构
- 3.3.2 如何在CUDA中管理内存(分配、释放、数据传输)?
- 3.3.3 什么是核函数(Kernel)?如何定义和调用?
- 3.3.4 解释CUDA线程的层次结构
- 4. 视觉相关问题
- 4.1 如何计算一组数据的信息熵?
- 4.2 什么是过拟合和欠拟合?
- 4.3 什么是模型的偏差和方差?
- 4.4 准确度(Accuracy)、精确率(Precision)、召回率(Recall) 和 F1值
- 4.5 计算bbox的iou
- 4.6 在使用pytorch训练模型时,我只有32G的RAM, 如何训练512GB的数据呢?
- 4.7 Pytorch和tensorflow计算图区别
- 5. 模型部署
- 5.1 使用tensorrt10设计一个tensorrt模型推理的包装类
- 5.2 设计神经网络
- 6. AI问题
1. 数据结构
1.1 时间复杂度
以下述代码为例:
int n = 100;
for (int i = 0; i < n; i++)
{cout<<"test01"<<endl;
}
为了方便讨论,这里我们把每一条语句的执行时间都看做是一样的,记为一个时间单元
。
这里的,红色框皆为运行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)。
for (int i = 0; i < n; i++)
{for (int j = 0; j < n; j++){cout<<"test01"<<endl; // 最内层语句}
}
2. 算法问题
2.1 两数之和
// 暴力枚举法
class Solution {
public:vector<int> twoSum(vector<int>& nums, int target) {int n = nums.size();for (int i = 0; i < n; ++i) {for (int j = i + 1; j < n; ++j) {if (nums[i] + nums[j] == target) {return {i, j};}}}return {};}
};
2.2 递归求二叉树的深度
#include <iostream>
using namespace std;// 定义二叉树节点结构体
struct TreeNode {int val; // 节点值TreeNode *left; // 左子节点指针TreeNode *right; // 右子节点指针TreeNode(int x) : val(x), left(NULL), right(NULL) {} // 构造函数初始化
};// 递归计算二叉树的最大深度
int maxDepth(TreeNode* root) {// 如果节点为空,深度为 0if (root == NULL)return 0;// 递归求左子树和右子树的深度int leftDepth = maxDepth(root->left);int rightDepth = maxDepth(root->right);// 当前节点的深度等于左右子树深度的较大值加 1return max(leftDepth, rightDepth) + 1;
}int main() {// 创建一个简单的二叉树/*1/ \2 3/ \4 5*/TreeNode* root = new TreeNode(1);root->left = new TreeNode(2);root->right = new TreeNode(3);root->left->left = new TreeNode(4);root->left->right = new TreeNode(5);// 计算二叉树的最大深度cout << "The maximum depth of the binary tree is: " << maxDepth(root) << endl;return 0;
}
2.3 一个由0和1组成数组中,计算出这里面连续1的最大数
#include <iostream>
#include <vector>
using namespace std;int findMaxConsecutiveOnes(vector<int>& nums) {int maxCount = 0; // 最大连续1的数量int currentCount = 0; // 当前连续1的数量for (int i = 0; i < nums.size(); i++) {if (nums[i] == 1) {currentCount++; // 如果当前数字是1,增加连续1的数量} else {maxCount = max(maxCount, currentCount); // 遇到0时更新最大连续1的数量currentCount = 0; // 重置当前连续1的数量}}// 可能数组最后的元素是1,循环结束时没有机会更新最大值maxCount = max(maxCount, currentCount);return maxCount;
}int main() {vector<int> nums = {1, 1, 0, 1, 1, 1}; // 示例数组int result = findMaxConsecutiveOnes(nums);cout << "The maximum number of consecutive 1s is: " << result << endl;return 0;
}
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 超出作用域时,自动释放所指向的内存。
#include <iostream>
#include <memory>void example() {std::unique_ptr<int> ptr1(new int(10)); // 动态分配一个整数std::cout << *ptr1 << std::endl; // 输出 10// std::unique_ptr<int> ptr2 = ptr1; // 错误,不能复制std::unique_ptr<int> ptr2 = std::move(ptr1); // 转移所有权std::cout << *ptr2 << std::endl; // 输出 10// ptr1 现在为空
}
3.1.1.2 std::shared_ptr共享指针
特性:
1.
表示对动态分配对象的共享所有权
。
2.
允许多个 shared_ptr 指向同一个对象,使用引用计数
来管理内存。
3.
当最后一个指向该对象的 shared_ptr 被销毁时,内存将被释放
。
#include <iostream>
#include <memory>void example() {std::shared_ptr<int> ptr1(new int(20)); // 动态分配一个整数std::cout << *ptr1 << std::endl; // 输出 20std::shared_ptr<int> ptr2 = ptr1; // 共享所有权std::cout << *ptr2 << std::endl; // 输出 20std::cout << ptr1.use_count() << std::endl; // 输出 2,表示有两个指针共享同一对象
}
3.1.1.3 std::weak_ptr弱指针
特性:
1.
主要用于打破 shared_ptr 的循环引用
问题。
2.
不控制对象的生命周期,不增加引用计数。
3.
可以通过 lock() 方法获得一个 shared_ptr。
作用:
用来解决shared_ptr相互引用
导致的死锁问题
。
当两个或多个 std::shared_ptr 互相持有对方的引用时,它们的引用计数不会降到零,导致内存无法被释放,这就是所谓的循环引用或死锁
。
#include <iostream>
#include <memory>class B; // 前向声明class A {
public:std::shared_ptr<B> b_ptr;~A() { std::cout << "A destroyed\n"; }
};class B {
public:std::shared_ptr<A> a_ptr;~B() { std::cout << "B destroyed\n"; }
};int main() {std::shared_ptr<A> a(new A);std::shared_ptr<B> b(new B);a->b_ptr = b;b->a_ptr = a;// a 和 b 互相引用,导致无法释放return 0;
}
用 std::weak_ptr 解决问题
#include <iostream>
#include <memory>class B; // 前向声明class A {
public:std::shared_ptr<B> b_ptr;~A() { std::cout << "A destroyed\n"; }
};class B {
public:std::weak_ptr<A> a_ptr; // 使用 weak_ptr~B() { std::cout << "B destroyed\n"; }
};int main() {std::shared_ptr<A> a(new A);std::shared_ptr<B> b(new B);a->b_ptr = b;b->a_ptr = a; // 现在使用 weak_ptr// a 和 b 之间的引用关系不会阻止内存的释放return 0;
}
3.1.2 C++中的引用与指针的区别
引用
:
相当于给变量起“别名”
,在c++内部其实是一个“指针常量”
,必须初始化,且一旦初始化后,就不能改变。
指针
:
是一个变量,它存储另一个变量的内存地址。通过指针,可以访问和修改指针所指向的变量。
3.1.3 解释C++中的虚函数和如何使用它实现多态
虚函数
是在基类中用virtual
声明的函数,它允许在派生类中重写
。当使用基类指针
或引用指向子类
时候,程序会根据实际对象的类型(即派生类)来决定调用哪个版本的函数,而不是基类版本。这种行为称为“动态绑定”
或“晚绑定”。
3.1.4 解释重载、重写和隐藏在C++中的区别
1、重载
重载是指在同一个作用域中,可以定义多个同名
的函数或运算符,它们的参数列表必须不同(参数的数量或类型不同)。C++允许函数重载以提高代码的可读性和灵活性。编译时多态
。
2、重写
重写是指在派生类中重新定义基类中已经存在的虚函数。重写允许子类提供特定的实现,并且实现与基类的函数具有相同
的名称和参数列表。重写用于实现运行时多态性。动态绑定
。
3、隐藏
隐藏是指在派生类中定义了一个与基类中同名
的函数或变量,导致基类中的函数或变量在派生类中不可见。这种情况下,基类的同名成员函数或变量不会被重写,而是被隐藏。
#include <iostream>class Base {
public:void display() {std::cout << "Base class display function." << std::endl;}
};class Derived : public Base {
public:void display(int i) { // 隐藏基类的display函数std::cout << "Derived class display function with int: " << i << std::endl;}
};int main() {Derived d;d.display(10); // 调用派生类的display(int)函数// d.display(); // 错误: 基类的display()不可见d.Base::display(); // 调用基类的display()函数return 0;
}
3.1.5 解释C++中的构造函数和析构函数
构造函数:
是一种特殊的成员函数,在对象创建时被自动调用
。它的主要作用是初始化对象的属性或分配资源
。构造函数的名称与类名相同,并且没有返回类型(包括void)。
析构函数:
是一种特殊的成员函数,在对象生命周期结束时被自动调用
。它的主要作用是释放对象在构造时分配的资源(如内存、文件句柄等)
。析构函数的名称与类名相同,但在名称前加上~符号,并且没有参数和返回类型。
3.2 Python
python_310">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!”
python">import re
string = "xy sfas 1e-6 fs!"# 使用正则表达式匹配科学计数法
pattern = r'[-+]?\d*\.?\d+([eE][-+]?\d+)?'
matches = re.findall(pattern, string)# 打印匹配到的科学计数法部分
print(matches)
string = "xsdaaf2e+9as.
python">import re
string = "xsdaaf2e+9as"# 使用正则表达式匹配科学计数法
pattern = r'[-+]?\d*\.?\d+[eE][-+]?\d+'
matches = re.findall(pattern, string)# 打印匹配到的科学计数法部分
print(matches)
3.2.4 a,b两个变量在不使用中间变量的情况下如何交换值
python">a = 5
b = 10a, b = b, aprint(a) # 输出: 10
print(b) # 输出: 5
3.2.5 简述一下装饰器是什么,并设计一个统计函数运行时间的装饰器
3.2.6
(1) 定义一个名为car的类,它有两个属性:“color”和“speed”。然后创建一个实例并返回“speed”。
python">class Car:def __init__(self, color, speed):self.color = color # 定义颜色属性self._speed = speed # 使用单下划线标记为“保护”属性def get_speed(self):return self._speed # 返回 speed# 创建 Car 类的实例
my_car = Car(color="red", speed=120)# 返回 speed
car_speed = my_car.get_speed()
print(f"The speed of the car is: {car_speed} km/h")
(2) 如果我想要访问speed,又想让用户不能修改,怎么实现?(编程形式回答)
python">class Car:def __init__(self, color, speed):self.color = color # 定义颜色属性self._speed = speed # 使用单下划线标记为“保护”属性def get_speed(self):return self._speed # 返回 speed# 创建 Car 类的实例
my_car = Car(color="red", speed=120)# 访问 speed
car_speed = my_car.get_speed()
print(f"The speed of the car is: {car_speed} km/h")# 尝试修改 speed(这将不起作用)
try:my_car._speed = 150 # 尝试直接修改 speed
except AttributeError as e:print(f"Error: {e}")# 查看 speed 仍然是原来的值
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__修饰符定义,并且只能从主机代码调用。
__global__ void kernelName(参数列表) {// 核函数代码
}
调用核函数时,需要指定执行配置,包括线程块的数量和每个线程块中的线程数量:
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
python">def calculate_iou(bbox1, bbox2):x1_min, y1_min, x1_max, y1_max = bbox1x2_min, y2_min, x2_max, y2_max = bbox2# 计算交集x_inter_min = max(x1_min, x2_min)y_inter_min = max(y1_min, y2_min)x_inter_max = min(x1_max, x2_max)y_inter_max = min(y1_max, y2_max)# 交集的面积inter_area = max(0, x_inter_max - x_inter_min) * max(0, y_inter_max - y_inter_min)# 各自的面积area1 = (x1_max - x1_min) * (y1_max - y1_min)area2 = (x2_max - x2_min) * (y2_max - y2_min)# 并集的面积union_area = area1 + area2 - inter_area# IoUiou = inter_area / union_area if union_area > 0 else 0return iou# 示例
bbox1 = [0, 0, 2, 2]
bbox2 = [1, 1, 3, 3]
iou = calculate_iou(bbox1, bbox2)
print("IoU:", iou)
4.6 在使用pytorch训练模型时,我只有32G的RAM, 如何训练512GB的数据呢?
将数据分为多个小批次(mini-batches),每次只加载一个批次的数据到内存中进行训练。PyTorch 的 DataLoader 可以轻松实现这一点。
python">from torch.utils.data import DataLoader, Datasetclass MyDataset(Dataset):def __init__(self, data_file):# 读取数据文件(例如,使用 h5py, pandas 等)self.data = ... # 数据读取逻辑def __len__(self):return len(self.data)def __getitem__(self, idx):# 返回单个数据样本return self.data[idx]# 创建数据集和数据加载器
dataset = MyDataset("path/to/your/data")
data_loader = DataLoader(dataset, batch_size=32, shuffle=True)for batch in data_loader:# 进行模型训练pass
4.7 Pytorch和tensorflow计算图区别
Pytorch是动态计算图,tensorflow是静态计算图。
5. 模型部署
5.1 使用tensorrt10设计一个tensorrt模型推理的包装类
使用tensorrt10设计一个tensorrt模型推理的包装类,包含构造函数和forward函数。要求构造函数输入序列化模型路径,初始化完毕后,推理时模拟pytorch的forward函数效果,输入数据,输出最终结果。
5.2 设计神经网络
如何用pytorch设计一个简单的神经网络(三层卷积,两层全连接,激活函数Relu,池化,正则化层), 并生成随机数输入给网络,得出结果。
python">import torch
import torch.nn as nn
import torch.nn.functional as F
class SimpleCNN(nn.Module):def __init__(self):super(SimpleCNN, self).__init__()# 第一层卷积self.conv1 = nn.Conv2d(in_channels=1, out_channels=16, kernel_size=3, stride=1, padding=1) # 输入通道1,输出通道16self.bn1 = nn.BatchNorm2d(16) # Batch Normalizationself.pool = nn.MaxPool2d(kernel_size=2, stride=2) # 最大池化# 第二层卷积self.conv2 = nn.Conv2d(in_channels=16, out_channels=32, kernel_size=3, stride=1, padding=1)self.bn2 = nn.BatchNorm2d(32)# 第三层卷积self.conv3 = nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3, stride=1, padding=1)self.bn3 = nn.BatchNorm2d(64)# 全连接层self.fc1 = nn.Linear(64 * 7 * 7, 128) # 假设输入图像为28x28,经过池化后为7x7self.fc2 = nn.Linear(128, 10) # 输出10个类别def forward(self, x):# 第一层x = self.conv1(x)x = self.bn1(x)x = F.relu(x)x = self.pool(x)# 第二层x = self.conv2(x)x = self.bn2(x)x = F.relu(x)x = self.pool(x)# 第三层x = self.conv3(x)x = self.bn3(x)x = F.relu(x)x = self.pool(x)# 展平x = x.view(-1, 64 * 7 * 7)# 全连接层x = self.fc1(x)x = F.relu(x)x = self.fc2(x)return x
6. AI问题
1、是否独立部署GPT?