My 面经
项目介绍
QA
-
介绍项目
教育场景下多种行为识别的项目,是我研究生阶段第一个项目,也是第一次去解决一些结合实际业务场景,结合软硬件编程的一个项目。经过这个项目,基本上就强化了自己对项目问题的分析以及代码能力,规范化代码习惯,比如从一开始把所有代码不加规范的任意放置,到结合具体设计模式去进行封装,把功能去写成一个个类,用面向对象的方法去解决问题,以及打包编译成库进行发布与交付。当自己的代码在嵌入式芯片上成功运行时,有比较大的成就感。然后在商品识别的项目当中,事先是进行了大量的算法学习和查阅文档的工作的,而且在过程中,结合需求进行了功能的完善,从一开始只能去识别一个固定角度摄像头下的图片,到后面不断提升了精度和速度。 -
在项目中,学到了什么
代码性能的优化,一方面是精度吧,后面的ransac有,另一方面是速度,也是结合硬件加速的引擎,Tensorrt,然后华为海思的nnie,然后多媒体处理模块。sift用了gpu加速,cuda编程,也是基于前人的肩膀,用cmake去实现的cpp工程,就可以直接将cuda文件类似于cpp一样的去封装到自己的项目当中。 -
遇到了什么问题,如何解决的
有两个比较印象深刻的问题吧,一个是在算法层面,一个是在项目开发层面。在商品识别与匹配这个项目中,我们用了sift作为匹配算法,最一开始并没有考虑对其进行改进,后面遇到了匹配点过多,然后在卡阈值的时候有一些误判的时候,发现有很多点,由于光照、拍摄模糊程度等,其实是错误匹配的,所以需要将它们进行滤除。后面结合了最小二乘的思想,进行了随机抽样一致性的一个算法过滤。才有了更好的效果。另外,在训练神经网络方面,也是有一些技巧的,比如如何去数据增强,然后如何多GPU并行训练,以及网络结构的选取,如何解决多尺度目标检测,设置一些初始化参数等等。在项目开发方面,主要是一些由于前期考虑不周而给后面的继续开发造成的隐患。比如在智慧货架商品识别项目当中,一开始只是要求摄像头角度正拍,后面又要求有斜拍,用手机排,扩展功能。所以就得去事先进行代码模式的设计,而不是到用的时候再去重新封装改写。另外还有一个问题印象深刻,就是在使用第三方库的时候,没有深入理解其用法,就会容易想当然的去用,直接去套一些模板,但实际上并不符合,比如在opencv当中有一个imencode方法,其功能是将图片编码,但一定要指定是jpg还是png格式,因为图像格式不一样,其通道数不一样,那编码的方法也就不一样。我在本地调试的时候能够通过,但当打包交付时候,就会出现bug。 -
怎么样快速地融入团队?一方面是知识储备,另一方面是了解业务架构,边学习边做事
-
对自己学习能力如何判断?获得的成绩,项目经历,自学能力,从无到有。然后对自己的数学等理论功底还是有一些深入储备的。
-
平时学习怎么做到成绩名列前茅的?意识到学习的重要性,结合了自身的兴趣,然后学习的习惯,预习复习。保持这种状态,一直到毕业,就习惯性的把一些工作学习放到前面,不松懈有一种紧张感。
-
问,这个岗位具体是做什么类型的通讯呢?有哪些算法相关呢?
Star原则
Situation/Task 明确任务
1、任务类型和背景
Action 明确行动
1、现状分析
2、决定某种行动方式
Result 说明结果
结果怎样
从项目中学到了什么,经过一段时间的反思,认为项目中还有什么值得提升改进的地方
在测试时,遇到了什么问题,是如何解决的?
- 在图像算法相关项目中,遇到过输入为空的情况,这时候一般来说是用户的输入我们没有考虑周全,导致后面对空指针进行操作致使程序崩溃,需要加上用户的提示,防止程序崩溃。然后在算法中间处理的过程中,一般不会出什么问题,无非就是一些内存忘记释放导致泄漏的一些小问题,后经过修复都解决。最后输出阶段,由于需要将内存的数据编解码,解码时,是用jpg格式还是png格式,是否带图像透明度等一些小问题。
- 在前后端平台搭建的项目中,最核心遇到的问题其实还是网络通信的问题,比如在用html,跟一个网络摄像机进行搭建的时候,一开始用rtsp流,后来发现必须改成rtmp,然后还需要大家nginx服务器等一些操作。
视频理解项目:
- 项目目标:
- 任务难点:
- 任务分工:
- 解决方案:
- 评价指标:
目标分割项目:叶菜湿布
- 如何检测:erfnet
模型沿用了Encoder-Decoder结构,结合残差网络、整个网络包含23层,其中1-16层为Encoder,17-23层为Decoder。
Encoder:
- DownsampleBlock
- non_bottleneck_1d
self.initial_block = DownsamplerBlock(3, 16)
self.layers = nn.ModuleList()
for x in range(0, 5): # 5 times
self.layers.append(non_bottleneck_1d(16, 0.03, 1))
self.layers.append(DownsamplerBlock(16, 64))
self.layers.append(non_bottleneck_1d(64, 0.3, 2))
self.layers.append(non_bottleneck_1d(64, 0.3, 4))
self.layers.append(non_bottleneck_1d(64, 0.3, 8))
self.layers.append(non_bottleneck_1d(64, 0.3, 16))
其中:DownsamplerBlock
self.conv = nn.Conv2d(ninput, noutput - ninput, (3, 3), stride=2, padding=1, bias=True)
self.pool = nn.MaxPool2d(2, stride=2)
self.bn = nn.BatchNorm2d(noutput, eps=1e-3)
其中,non_bottleneck_1d
super(non_bottleneck_1d, self).__init__()
self.conv3x1_1 = nn.Conv2d(chann, chann, (3, 1), stride=1, padding=(1, 0), bias=True)
self.conv1x3_1 = nn.Conv2d(chann, chann, (1, 3), stride=1, padding=(0, 1), bias=True)
self.bn1 = nn.BatchNorm2d(chann, eps=1e-03)
self.conv3x1_2 = nn.Conv2d(chann, chann, (3, 1), stride=1, padding=(1 * dilated, 0), bias=True,
dilation=(dilated, 1))
self.conv1x3_2 = nn.Conv2d(chann, chann, (1, 3), stride=1, padding=(0, 1 * dilated), bias=True,
dilation=(1, dilated))
self.bn2 = nn.BatchNorm2d(chann, eps=1e-03)
self.dropout = nn.Dropout2d(dropprob)
其中,dilated conv膨胀卷积,也叫空洞卷积。结构图如下:
Decoder:
- UpsamplerBlock
- non_bottleneck_1d
其中UpsamplerBlock
self.conv = nn.ConvTranspose2d(ninput, noutput, 3, stride=2, padding=1, output_padding=1, bias=True)
self.bn = nn.BatchNorm2d(noutput, eps=1e-3)
loss function
使用的是cross_entropy2d(outputs, labels, weight = class_weight)
本质上,就是将两个输入,outputs即最后的feature map和label,都是二维,进行upsample操作到相同大小之后,进行view展平,然后经过cross_entropy操作即可。
def cross_entropy2d(input, target, weight=None, size_average=True):
n, c, h, w = input.size()
nt, ht, wt = target.size()
# Handle inconsistent size between input and target
if h > ht and w > wt: # upsample labels
target = target.unsequeeze(1)
target = F.upsample(target, size=(h, w), mode="nearest")
target = target.sequeeze(1)
elif h < ht and w < wt: # upsample images
input = F.upsample(input, size=(ht, wt), mode="bilinear")
elif h != ht and w != wt:
raise Exception("Only support upsampling")
input = input.transpose(1, 2).transpose(2, 3).contiguous().view(-1, c)
target = target.view(-1)
loss = F.cross_entropy(input, target, weight=weight, size_average=size_average, ignore_index=250)
return loss
- 如何转化成项目需求:为什么检测蔬菜而不是检测布
- 如何分析波形,包括滤波,取分析面积等(具体细节还可能包括求连通域等)
- 如何沟通,本来是检测动作——>检测状态——>本来是检测既定时间内——>扩展10分钟——>本来是分析持续进行——>断点保存,可以同时运行多路
商品识别项目:
SIFT原理以及应用
SIFT全称:scale-invariant feature transform 。尺度不变特征变换,其实叫做尺度变换而特征不变更合理。
SIFT步骤
https://zhuanlan.zhihu.com/p/22476595
SIFT 算法具的特点
图像的局部特征,对旋转、尺度缩放、亮度变化保持不变,对视角变化、仿射变换、噪声也保持一定程度的稳定性。
独特性好,信息量丰富,适用于海量特征库进行快速、准确的匹配。
多量性,即使是很少几个物体也可以产生大量的 SIFT 特征
高速性,经优化的 SIFT 匹配算法甚至可以达到实时性
扩招性,可以很方便的与其他的特征向量进行联合。
SIFT 特征检测的步骤有 4 个主要步骤
尺度空间的极值检测 :搜索所有尺度空间上的图像,通过高斯微分函数来识别潜在的对尺度和选择不变的兴趣点。
特征点定位 :在每个候选的位置上,通过一个拟合精细模型来确定位置尺度,关键点的选取依据他们的稳定程度。
特征方向赋值 :基于图像局部的梯度方向,分配给每个关键点位置一个或多个方向, 后续的所有操作都是对于关键点的方向、尺度和位置进行变换,从而提供这些特征的不变性。
特征点描述 :在每个特征点周围的邻域内,在选定的尺度上测量图像的局部梯度, 这些梯度被变换成一种表示,这种表示允许比较大的局部形状的变形和光照变换。
步骤可以主要分成两步:
- 特征点检出
- 特征点描述
特征点检出主要是用了DoG,就是把图像做不同程度的高斯模糊blur,平滑区域或点肯定变化不大,而纹理复杂的比如边缘,点、角之类区域肯定变化很大,这样变化很大的点就是特征点。当然,为了找到足够的点,还需要把图像放大缩小几倍(Image Pyramids)来重复这个步骤找特征点。其实DoG并不是Lowe提出的,可替代特征点检出的还有很多其他方法如MSER。
特征点描述就是一个简单的HOG,即以检出的特征点为中心选16x16的区域作为local patch,这个区域又可以均分为4x4个子区域,每个子区域中各个像素的梯度都可以分到8个bin里面,这样就得到了4x4x8=128维的特征向量。特征点检出以后还需要一个很重要的步骤就是归一化,计算这个patch的主方向,然后根据这个主方向把patch旋转到特定方向,这样计算的特征就有了方向不变性,也需要根据patch各像素梯度大小把patch缩放到一定的尺度,这样的特征就有了尺度不变性。
归纳来说,SIFT算法的实质可以归为在不同尺度空间上查找特征点的问题。
https://blog.csdn.net/qq_39451645/article/details/112129039
其实确切来说SIFT是指第二步的特征,而SIFT features或SIFT descriptor的说法比较准确。
想要看数学解释:https://blog.csdn.net/jancis/article/details/80824793
想要通俗理解:https://blog.csdn.net/qq_39451645/article/details/112129039
GPU加速版SIFT
先detection yolov5 VOCDevkit商品数据集(公开和自行标注)
- 项目目标:找出颜色数目排名前三的颜色作为该图片的主颜色,通过两幅图片主颜色比对判定两幅图是否不一致。判定方式为:
三种主要颜色在保证第一大类颜色必然能对应的同时,仍存在第二种颜色一致,否则认为两张图片不同。颜色一致可由SIFT或文字识别进一步比对。
为避免噪点影响,使用连通区域检测思想进行颜色不同类别数量统计(并查集)。
2. 基本知识:
检测颜色信息时往往用HSV空间而不是RGB空间,是因为RGB通道并不能很好的反映出物体具体的颜色信息。但HSV在用于指定颜色分割时,有比较大的作用,H和S分量代表了色彩信息。颜色距离代表两种颜色之间的数值差异。对于不同的色彩区域,混合H与S分量,划定阈值,即可进行简单的分割。
-
Hue用角度度量,取值范围为0~360°,表示色彩信息,即所处的光谱颜色的位置,Hue = 0表示红色,Hue = 120表示绿色,Hue = 240表示蓝色等。
-
-
Saturation表示饱和度,饱和度表示颜色解决光谱色的程度。饱和度越高,说明颜色越深,越接近光谱色饱和度越低,说明颜色越浅,越接近白色。饱和度为0表示纯白色。取值范围为0~100%
-
竖直方向表示明度,决定颜色空间中颜色的明暗程度,明度越高,表示颜色越明亮,范围是0~100%,明度为0表示纯黑色。
- 任务难点:
- 颜色种类较少,图片描述过于模糊,不利于近似颜色比较。
- 其他颜色物品反射光对颜色比较影响较大。
- 分割单个商品时较低商品包含较多背景区域,引起误判。
- 光线较暗时,颜色检测趋于灰黑。
- 主要负责:
256维颜色分类判定距离
合成特征矢量:L = 16 * H + 4 * S + V
距离判断
得到的结果距离越接近1,两幅图越相似,暂取0.5为对比阈值
得到两幅图特征矢量分别为X,Y,其距离为 (n=256)
D =
∑
1
n
m
i
n
(
x
i
,
y
i
)
m
i
n
(
∑
1
n
x
i
,
∑
1
n
y
i
)
\sum_1^n min(x_i,y_i) \over min(\sum_1^n x_i, \sum_1^n y_i)
min(∑1nxi,∑1nyi)∑1nmin(xi,yi)
最大编辑距离(Levenshtein距离):是指两个字串之间,由一个转成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。一般来说,编辑距离越小,两个串的相似度越大。
https://leetcode-cn.com/problems/edit-distance/solution/shi-pin-jiang-jie-bian-ji-ju-chi-dong-tai-gui-hua-/
如何将该算法应用到分析商品陈列情况的呢?(学以致用环节,将商品唯一的条形码作为字符即可)
编辑过程:
str2=a b c d e f b c d
str1=b c d a b c d e f
1、向左走,即 d[ 1,1] = d[ 1-1,1 ]+1 , str2 要删除第一个字符,变为 bcdefbcd
2、斜向下,且值未变,说明相同,不用操作
str2 =b c d e f b c f
str1 =b c d a b c d e f
3、 d 之后向左,即删除 e
4、斜向下,且值加1,表示替换,将f换为a
str2 =b c d a b c f
str1 =b c d a b c d e f
5、最后两步向下,表示添加,此处添加 e, f
str2 = b c d a b c d e f
str1 = b c d a b c d e f
共5步操作。
回溯路径时要从右下角的元素开始,依次看当前元素是如何得到的,有时一个元素可能有多种得到的方式,即表明可以有多种操作可以得到相同的结果。上图的红色箭头即为回溯路径。将回溯路径再反过来就可以得到实际编辑操作的路径。
向左走,即dp[m][n] = dp[m - 1][n] + 1,表示删除一个字符
斜向下,且值未变,说明相同,不用操作
斜向下,且
https://blog.csdn.net/alansede/article/details/48103169
算法优化
为了使结果更加准确,将第一步判定认为一致的两幅图分别分为四份,将四份图片对应重复HSV判断,如果有两块及以上区域相似,认为两幅图相似。
-
c++部署
本地调试程序 ——> ARM平台打包静态库(因为没有安装交叉编译环境)——> 代码加密md5、模型加密 ——> 提交测试 -
评价指标:商品检测性能(瓶类)、陈列情况分析(是否正确)
生鲜项目
生鲜牌识别主要分为文字识别与数字识别两部分,文字识别主要采用paddleocr实现,数字识别则通过opencv对图片二值化处理后使用yolov3进行识别。部署于TX2上的yolov3采用tensorrt辅助加速
paddleOCR 3.5M中英文超轻量模型
包括自然场景文字识别和文档场景文字识别
文本检测、检测框矫正、文本识别
基于分割的文字识别方案!而不是检测(AAAI2020)
文本检测框矫正:几何变换和文本方向分类器
文本识别:CRNN,卷积特征和序列特征融合,挖掘特征上下文信息。CTC损失解决预测结果和标签不一致。神经网络预测的结果都是固定长度,但序列标签可能变化长
度。
ATTENTION机制或者transformer,在英文上效果不错,但是由于中文字符太多6000+,且由于中文语义的问题,挖掘两个字之间的上下文信息还是很困难的。(实验中,去掉lstm也不会很大幅度上影响检测准确率)
19个策略全方位优化瘦身:网络结构微调、数据增强、学习率变换策略、剪枝量化
合成数据(比如0~9数字)
backbone:mobilenet-V3(在之前深度可分离思想的基础上,使用h-wish而不是ReLU6,扩展层使用的滤波器数量不同(使用NetAdapt算法获得最佳数量),瓶颈层输出的通道数量不同(使用NetAdapt算法获得最佳数量),Squeeze-and-excitation模块(SE)将通道数仅缩减了3或4倍,对于SE模块,不再使用sigmoid,而是采用ReLU6(x + 3) / 6作为近似(就像h-swish那样))
https://zhuanlan.zhihu.com/p/260790699
https://zhuanlan.zhihu.com/p/70703846
智慧课堂校园大脑项目:
硬件调试技能和c++的熟练运用(静态库打包等)
计算机网络八股文
数据结构基础知识
-
最大堆最小堆的概念,
堆树的定义如下:
(1)堆树是一棵完全二叉树
(2)堆树中某个节点的值总是不大于或不小于其子节点的值
(3)堆树中每个节点的子树都是堆树
当父节点的键值总是大于或等于任何一个子节点的键值时为最大堆。 当父节点的键值总是小于或等于任何一个子节点的键值时为最小堆
堆树的操作
(1)构造最大堆:
首先将每个叶子节点视为一个堆,再将每个叶子节点与其父节点一起构成一个包含更多节点的堆。所以,在构造堆的时候,首先需要找到最后一个节点的父节点,从这个节点开始构造最大堆;直到该节点前面所有分支节点都处理完毕,这样最大堆就构造完毕。(2)最大堆中插入节点
先在堆的最后添加一个节点,然后沿着堆树上升。跟最大堆的初始化过程大致相同。
(3)最大堆中堆顶节点的删除
将堆树的最后的节点提到根节点,然后删除最大值,然后再把新的根节点放到合适的位置。堆树的应用: 利用最大堆最小堆排序(堆排序算法详解:http://blog.csdn.net/guoweimelon/article/details/50904231)
基本思想:从最大堆的顶部不断取走堆顶元素放到有序序列中,直到堆的元素被全部取完。算法流程如下:- 创建一个最大(小)堆H
- 把堆首和堆尾元素互换
- 把堆的大小减一,重新构造一个最大(小)堆
- 重复步骤2、3,直到堆的大小为1
https://blog.csdn.net/guoweimelon/article/details/50904346#:~:text=%E6%9C%80%E5%A4%A7%E5%A0%86%EF%BC%9A%E6%A0%B9%E7%BB%93%E7%82%B9,%E7%84%B6%E5%90%8E%E6%B2%BF%E7%9D%80%E6%A0%91%E4%B8%8A%E5%8D%87%E3%80%82
-
归并排序时间复杂度
归并排序的性能不受输入数据的影响,但表现比选择排序好很多,因为始终都是O(nlogn)的时间复杂度。代价是需要额外的内存空间。
归并排序(Merge Sort)是建立在归并操作上的一种有效的排序算法。该算法是采用分治法的一个非常典型的应用。作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法。- 自上而下的递归
大问题分成若干子问题之后,对于每个子问题分别求解,求解的方式跟求解原问题的方式一样,所以产生了递归。由于所有的递归方法都可以用迭代重写,所以就有了第二种方法。
- 自上而下的递归
void mergeSort(T *a, int left, int right)
{
// 对数组元素a[left, right]进行排序
if (left < right)
{
// 至少有两个元素
int middle = (left + right) / 2;
mergeSort(a, left, middle);
mergeSort(a, middle + 1, right);
merge(a, b, left, middle, right); // 从a到b归并
copy(b, a, left, right); // 将排序结果复制到a
}
}
void Merge(int arr[], int left, int middle, int right)
{ //归并操作
int length = right - left + 1;
int *pArr;
int beginA = left,beginB = middle + 1; //设置两个标志,分别指向两个已排序序列的起始位置
int nCount = 0,i;
pArr = (int *)malloc(sizeof(int)*length); //开辟空间
if (pArr == NULL)
{
printf("malloc error\n");
return;
}
while (beginA <= middle)
{
if (arr[beginA] > arr[beginB])
{
pArr[nCount++] = arr[beginB++];
}
if (arr[beginA] < arr[beginB])
{
pArr[nCount++] = arr[beginA++];
}
if (beginB > right) break;
}
while (beginA <= middle)
{
pArr[nCount++] = arr[beginA++];
}
while (beginB <= right)
{
pArr[nCount++] = arr[beginB++];
}
for (i = 0; i < length; i++) //把排序好的部分移回arr数组中
{
arr[left++] = pArr[i];
}
free(pArr);
}
- 自下而上的迭代
void mergeSort(T a[], int n)
{
T *b = new T [n];
int segmentSize = 1;
while(segmentSize < n)
{
mergePass(a, b, n, segmentSize);
segmentSize += segmentSize;
mergePass(b, a, n, segmentSize);
segmentSize += segmentSize;
}
delete[] b;
}
void mergePass(T)
- 稳定排序和非稳定排序,
- 二叉搜索树的递归定义
算法题 股票买卖一次交易
风控策略算法工程师:
1、NC62平衡二叉树
服务端测试开发工程师:
1、NC20数字字符串转化成IP地址
多模态算法工程师:
1、NC25删除有序链表中重复的元素
2、NC88寻找第K大
测试开发工程师:
1、NC4判断链表中是否有环
2、NC91最长递增子序列
3、NC28最小覆盖子串
(动态规划问题)
过河问题
小明需要踩着石头过河。
下一步只能到达距离为3、4、5石头。
给一个数组,里面是n个石头,以及石头到岸边的距离。
假设从小到大排序。
图卷积涉猎
GCN的原理
笔者从直觉角度(特征聚合,特征变换)回答,这个角度与空域图卷积非常类似(实际上GCN有着充分的理论基础保证,所以还应该从谱图卷积的角度回答,详见问题5)
GCN有什么问题
笔者仍然从 Over smoothing,梯度消失等方面回答
5.如何从标准的谱图卷积过渡到现在的典型GCN。
谱图卷积——切比雪夫多项式近似频域卷积核——取一阶近似并对切比雪夫系数进行化简
强化学习涉猎
Q-Learning和DQN的区别?
这个是强化学习的问题,笔者只了解过一些经典强化学习算法,并未真正参与过相关项目。
DQN使用一个NN代替Q-Learning中的Q表,Q-Learning无法处理某些状态空间S非常大的场景(Q表太大,无法存储和更新),所以使用神经网络代替Q表输出Q(s, a)。
常用深度学习算法介绍
概述
项目中用到的算法
目标检测
目标分割
目标跟踪
风控相关
一面:
问了道概率题,患病概率0.001,患病中检测出的概率为0.95,未患病中检测出的概率为0.05,问检测出的患病概率为多少?
后验概率是(0.0010.95)/(0.0010.95+0.05*0.999)
evidence也是P(检测),后验概率P(有病|检测)就是等于P(检测|有病)*P(有病)/(P(检测|有病)*P(有病)+P(检测|没病)*P(没病))
附录:
编辑距离回溯代码
#pragma once
#include <vector>
#include <bits/stdc++.h>
using namespace std;
#define ov "no_temp"
#define _n "blank"
template<class T>
class Distance {
public:
typedef pair<int, vector<char>> fNode;
typedef pair<vector<T>,vector<T>> respair;
Distance(const vector<T>& temp, const vector<T>& fact,int cost_del = 1, int cost_ins = 2, int cost_err = 1, int cost_dai = 0);
vector<respair> traceback(int i , int j);
vector<respair> traceback();
int getDis();
void print_aligns(vector<string> & a,vector<string>& b,int mode = 0);
private:
vector<T> temp;
vector<T> fact;
vector<vector<fNode>> table;
};
template<class T>
Distance<T>::Distance(const vector<T> &temp, const vector<T> &fact, int cost_del, int cost_ins, int cost_err,int cost_dai):
temp(temp),fact(fact)
{
int n1 = temp.size() + 1;
int n2 = fact.size() + 1;
int dis[3];
table.resize(n1);
for(int i = 0; i < n1; i ++)
{
table[i].resize(n2);
}
for (int i = 1; i < n1; i++) table[i][0] = pair<int, vector<char>>(i, {'_'});
for (int i = 1; i < n2; i++) table[0][i] = pair<int, vector<char>>(i, {'|'});
for (int i = 1; i < n1; i++)
for (int j = 1; j < n2; j++)
{
dis[0] = table[i - 1][j].first + cost_del;
dis[1] = table[i][j - 1].first + cost_ins;
dis[2] = table[i - 1][j - 1].first + cost_err;
if(temp[i - 1] == fact[j - 1] || temp[i - 1] == ov)dis[2] = table[i - 1][j - 1].first;
else dis[2] = table[i - 1][j - 1].first + cost_err;
auto min = *min_element(dis,dis+3);
table[i][j].first = min;
if(dis[0] == min) table[i][j].second.emplace_back('_');
if(dis[1] == min) table[i][j].second.emplace_back('|');
if(dis[2] == min) table[i][j].second.emplace_back('/');
}
}
template<class T>
vector<typename Distance<T>::respair> Distance<T>::traceback(int i, int j)
{
if(i == 0 && j == 0){
return vector<respair> {respair {}};
}
vector<respair> ret;
for(auto c : table[i][j].second)
{
if(c == '|')
{
for(auto p : traceback(i,j-1))
{
vector<T> a,b;
a.insert(a.end(),p.first.begin(),p.first.end());
a.emplace_back(_n);
b.insert(b.end(),p.second.begin(),p.second.end());
b.emplace_back(fact[j - 1]);
ret.emplace_back(respair {a,b});
}
}
if(c == '/')
{
for(auto p : traceback(i-1,j-1))
{
vector<T> a,b;
a.insert(a.end(),p.first.begin(),p.first.end());
a.emplace_back(temp[i - 1]);
b.insert(b.end(),p.second.begin(),p.second.end());
b.emplace_back(fact[j - 1]);
ret.emplace_back(respair {a,b});
}
}
if(c == '_')
{
for(auto p : traceback(i-1,j))
{
vector<T> a,b;
a.insert(a.end(),p.first.begin(),p.first.end());
a.emplace_back(temp[i - 1]);
b.insert(b.end(),p.second.begin(),p.second.end());
b.emplace_back(_n);
ret.emplace_back(respair {a,b});
}
}
}
return ret;
}
template<class T>
vector<typename Distance<T>::respair> Distance<T>::traceback()
{
int i = temp.size();
int j = fact.size();
if(i == 0 && j == 0){
return vector<respair> {respair {}};
}
vector<respair> ret;
for(auto c : table[i][j].second)
{
if(c == '|')
{
for(auto p : traceback(i,j-1))
{
vector<T> a,b;
a.insert(a.end(),p.first.begin(),p.first.end());
a.emplace_back(_n);
b.insert(b.end(),p.second.begin(),p.second.end());
b.emplace_back(fact[j - 1]);
ret.emplace_back(respair {a,b});
}
}
if(c == '/')
{
for(auto p : traceback(i-1,j-1))
{
vector<T> a,b;
a.insert(a.end(),p.first.begin(),p.first.end());
a.emplace_back(temp[i - 1]);
b.insert(b.end(),p.second.begin(),p.second.end());
b.emplace_back(fact[j - 1]);
ret.emplace_back(respair {a,b});
}
}
if(c == '_')
{
for(auto p : traceback(i-1,j))
{
vector<T> a,b;
a.insert(a.end(),p.first.begin(),p.first.end());
a.emplace_back(temp[i - 1]);
b.insert(b.end(),p.second.begin(),p.second.end());
b.emplace_back(_n);
ret.emplace_back(respair {a,b});
}
}
}
return ret;
}
template<class T>
int Distance<T>::getDis() {
return table[temp.size()][fact.size()].first;
}
template<class T>
void Distance<T>::print_aligns(vector<string> & a,vector<string>& b,int mode) {
vector<Distance<string>::respair> res = this->traceback();
cout << "min dis:" << this->getDis() << endl;
if(mode == 0)
{
for(const auto& r : res)
{
for(const auto& c : r.first){
if(c == _n)
{
a.emplace_back("_");
continue;
}
a.emplace_back(c);
}
for(const auto& c : r.second){
if(c == _n)
{
b.emplace_back("_");
continue;
}
b.emplace_back(c);
}
}
} else
{
for(int i = 0; i < mode && i < res.size(); i++)
{
auto r = res[i];
for(const auto& c : r.first){
if(c == _n)
{
a.emplace_back("_");
continue;
}
a.emplace_back(c);
}
for(const auto& c : r.second){
if(c == _n)
{
b.emplace_back("_");
continue;
}
b.emplace_back(c);
}
}
}
}
字节跳动校招面试精髓
https://www.nowcoder.com/discuss/373163