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) {
- // 如果节点为空,深度为 0
- if (root == NULL)
- return 0;
- // 递归求左子树和右子树的深度
- int leftDepth = maxDepth(root->left);
- int rightDepth = maxDepth(root->right);
- // 当前节点的深度等于左右子树深度的较大值加 1
- return 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; // 输出 20
-
- std::shared_ptr<int> ptr2 = ptr1; // 共享所有权
- std::cout << *ptr2 << std::endl; // 输出 20
- std::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
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!”
- import re
- string = "xy sfas 1e-6 fs!"
- # 使用正则表达式匹配科学计数法
- pattern = r'[-+]?\d*\.?\d+([eE][-+]?\d+)?'
- matches = re.findall(pattern, string)
- # 打印匹配到的科学计数法部分
- print(matches)
复制代码 string = "xsdaaf2e+9as.
- import re
- string = "xsdaaf2e+9as"
- # 使用正则表达式匹配科学计数法
- pattern = r'[-+]?\d*\.?\d+[eE][-+]?\d+'
- matches = re.findall(pattern, string)
- # 打印匹配到的科学计数法部分
- print(matches)
复制代码 3.2.4 a,b两个变量在不使用中心变量的环境下如何交换值
- a = 5
- b = 10
- a, b = b, a
- print(a) # 输出: 10
- print(b) # 输出: 5
复制代码 3.2.5 简述一下装饰器是什么,并设计一个统计函数运行时间的装饰器
3.2.6
(1) 界说一个名为car的类,它有两个属性:“color”和“speed”。然后创建一个实例并返回“speed”。
- 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,又想让用户不能修改,怎么实现?(编程形式回答)
- 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
- def calculate_iou(bbox1, bbox2):
- x1_min, y1_min, x1_max, y1_max = bbox1
- x2_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
-
- # IoU
- iou = inter_area / union_area if union_area > 0 else 0
- return 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 可以轻松实现这一点。
- from torch.utils.data import DataLoader, Dataset
- class 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,池化,正则化层), 并生成随机数输入给网络,得出结果。
- 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,输出通道16
- self.bn1 = nn.BatchNorm2d(16) # Batch Normalization
- self.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,经过池化后为7x7
- self.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?
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |