第一篇博客--记面陌陌科技计算机视觉算法工程师被刷经历(附面试过程中被问倒的一些算法题分析)

求职季,真的会让一个人变得有些不一样吧,比如我,对于一个菜鸟来说,最近一段时间焦虑不安外加有点自闭...

前段时间在校内网上看到了陌陌科技内推计算机视觉算法工程师和机器学习算法工程师的消息,抱着试试的心态整理了一份简历按照提供的邮箱投出去了,我想这次应该又是石沉大海了吧,谁想在一周前闷热的一天在嘈杂的餐厅接到了陌陌科技HR的电话,一周后的周五下午4点在西安的一家咖啡馆参加面试。我问清了时间地点并道谢了HR后并挂了电话继续吃饭。

好吧,这周每天都有各个公司的笔试,外加这周周五上午的组会轮到我做组会汇报,我心里预估了一下时间安排,确实没时间来准备陌陌的面试,心想,就这样吧,面挂了就当积累经验吧...

时间很快就来到了周四晚上,当我9点做完招商银行的网上笔试后,打来之前没有写完的明天组会汇报的ppt接着写了起来,前两天已经连续凌晨2点回宿舍了,今晚不知何时能回。我主要给大家汇报一下近期的工作以及一篇临时看的发表在ICB2018上的使用GAN来完成从热红外到可见光的跨频谱人脸匹配的文献。时间来到11点半,ppt算是写得差不多了,但是文献中还是有很多细节问题因为时间关系没有搞懂,这篇文献里最大创新点也就是所提出的损失函数理解得云里雾里,我是继续加班搞懂才回宿舍呢,还是就这样将这个问题放在组会上大家一起讨论,可是明天还得早起啊。有那么一瞬间,我感觉呼吸不太顺畅,身体超负荷运转已经吃不消了。我选择回宿舍休息,就这样吧...

昨晚还是没有睡好,已经很久没有睡好觉了,不过相对于前两天,已经很不错了,9点组会开始,疲倦始终围绕着我,不出所料,这次组会因为各种因素我算是搞砸了...

组会完后,回到实验室给手机充电,打印了一份简历,吃完午饭回来打开百度地图搜索那家咖啡馆的地理位置并做好时间路线规划,等待手机充满电后我便出发了,下午3点我提前一个小时到了那家咖啡馆,我微信上给HR发消息说我到了进门之后怎么走,那位帅气的HR带我进了咖啡馆,问了下我的姓名和求职岗位,带我去签完到后给我找一个位置并了给了我一张A4纸,然后就是用自己熟悉的语言实现两道算法题,我周围的人都是今天来面试陌陌的,他们都在认真的低着头写代码。

第一题是实现输出一个长度为n的无序数组中的前k个最小值:

我能想到的就是先通过各种排序算法将数组排序,然后输出前k个最小值就行了,但是这并不是最好的方式,会造成复杂度比较高,因为只需要输出前k个最小值,剩下的n-k个数值不需要考虑。那么通过排序算法只需要排前k个数值ok了,不同的排序算法时间复杂度都是不一样的,比如比较容易实现的选择排序和冒泡排序平均情况下都是O(n2),若只需要找到前面的k个值复杂度也要O(n*k),若使用快速排序,复杂度近似O(n),如果使用堆排序,复杂度近似O(nlogk),下面基于堆排序给出解题思路以及python3代码:

方法是维护k个元素的最大堆,即用容量为k的最大堆存储最先遍历到的k个数,并假设它们即是最小的k个数,建堆费时O(k)后,有k1<k2<...<kmax(kmax设为大顶堆中最大元素)。继续遍历数列,每次遍历一个元素x,与堆顶元素比较,x<kmax,更新堆(用时logk),否则不更新堆。这样下来,总费时O(k+(n-k)*logk)=O(n*logk)。此方法得益于在堆中,查找等各项操作时间复杂度均为logk,python3代码如下:

 1 # -*- coding: utf-8 -*-
 2 """
 3 Created on Sun Sep  2 17:16:36 2018
 4
 5 @author: aoanng
 6 """
 7
 8 def create_heap(lyst):
 9     #创建数组中前k个数的最大堆
10     for start in range((len(lyst) - 2) // 2, -1, -1):
11         sift_down(lyst, start, len(lyst) - 1)
12
13
14     return lyst
15
16 # 堆排序,对于本问题用不着
17 def heapSort(lyst):
18     # 堆排序
19     for end in range(len(lyst) - 1, 0, -1):
20         lyst[0], lyst[end] = lyst[end], lyst[0]
21         sift_down(lyst, 0, end - 1)
22     return lyst
23
24 # 最大堆调整
25 def sift_down(lst, start, end):
26     root = start
27     while True:
28         child = 2 * root + 1
29         if child > end:
30             break
31         if child + 1 <= end and lst[child] < lst[child + 1]:
32             child += 1
33         if lst[root] < lst[child]:
34             lst[root], lst[child] = lst[child], lst[root]
35             root = child
36         else:
37             break
38
39
40 #测试
41 if __name__ == ‘__main__‘:
42     list1 = [50, 45, 40, 20, 25, 35, 30, 10, 15]
43     k = 4 #设置需要输出的前k个最小值
44     list_n_k = list1[k:]
45     heap_k = create_heap(list1[:k]) #将数组前k个数创建最大堆,并假设它们是最小的k个数
46     for i in range(len(list_n_k)):
47         if list_n_k[i]<heap_k[0]:
48             heap_k[0] = list_n_k[i]
49             heap_k = create_heap(heap_k) #更新堆
50     print(heap_k) #输出前k个未排序的最小值
51
52     #若需要,则可以对堆进行排序
53     heap_k_sort = heapSort(heap_k)
54     print(heap_k_sort)

第二题是给出一个n*n的矩阵,将其逆时针旋转90度,但是不能开辟新的内存空间:

这题的前提是必须在原数组上进行旋转操作,只要搞清楚矩阵中元素旋转的规律就容易求解了,那就是当前元素ai,j经过逆时针旋转90度后有ai,j=aj,(n-i)的关系。

写完这两道算法题后,我检查了两遍并在那儿扣手机,hr看见我写完之后过来收走了我的作业,让我等待一会儿,大概20分钟后一位技术面试官带着我的简历以及之前写好的算法题过来找我,第一轮技术面试便开始了。

我先简单的自我介绍后,面试官看着我的写的那两道算法题聊了起来,让我说说我的思路,我说第一题其实就是一个排序算法问题,然后输出前k个值就好,看见我用的选择排序算法,面试官指出了疑问,说这样时间复杂度会比较高,然后问我有没有其他的思路,我说可以只需要排前k个值将时间复杂度降到O(n*k),面试官最后逐步的引导我,说用最大堆会比较好,总之面试官人很nice,问到我不会的,总是在引导我。

然后就是介绍我简历中做过的几个项目,项目当中有用到深度学习平台tensorflow和CNN网络架构以及一些机器学习算法。面试官逐个问我,比如在ttensorflow中怎样构建一个cnn网络,防止过拟合的一些tips,Dropout是怎样工作的等等,然后让我手写在tensorflow中怎样保存模型和加载模型,tf.get_variable和tf.Variable的区别,tf.variable_scope和tf.name_scope的用法和区别,这些其实我平时项目中也有用到,平时也关注过这个问题,只是没有上心,当时没有回答上来,然后面试官大致的给我讲了一下原理就跳过这个问题了,回来后我又在网上查了一下资料,总结如下:

tf.variable_scope和tf.name_scope的用法:

tf.variable_scope可以让变量有相同的命名,包括tf.get_variable得到的变量,还有tf.Variable的变量

tf.name_scope可以让变量有相同的命名,只是限于tf.Variable的变量

例如:

 1 import tensorflow as tf;
 2
 3 with tf.variable_scope(‘V1‘):
 4     a1 = tf.get_variable(name=‘a1‘, shape=[1], initializer=tf.constant_initializer(1))
 5     a2 = tf.Variable(tf.random_normal(shape=[2,3], mean=0, stddev=1), name=‘a2‘)
 6 with tf.variable_scope(‘V2‘):
 7     a3 = tf.get_variable(name=‘a1‘, shape=[1], initializer=tf.constant_initializer(1))
 8     a4 = tf.Variable(tf.random_normal(shape=[2,3], mean=0, stddev=1), name=‘a2‘)
 9
10 with tf.Session() as sess:
11     sess.run(tf.global_variables_initializer())
12     print (a1.name)
13     print (a2.name)
14     print (a3.name)
15     print (a4.name)
16
17 #输出:
18 ‘‘‘
19 V1/a1:0
20 V1/a2:0
21 V2/a1:0
22 V2/a2:0
23 ‘‘‘

如果将上边的tf.variable_scope换成tf.name_scope将会报错:

Variable a1 already exists, disallowed. Did you mean to set reuse=True or reuse=tf.AUTO_REUSE in VarScope?...

改成如下这样就ok了:

 1 import tensorflow as tf
 2
 3 with tf.name_scope(‘V1‘):
 4 #    a1 = tf.get_variable(name=‘a1‘, shape=[1], initializer=tf.constant_initializer(1))
 5     a2 = tf.Variable(tf.random_normal(shape=[2,3], mean=0, stddev=1), name=‘a2‘)
 6 with tf.name_scope(‘V2‘):
 7 #    a3 = tf.get_variable(name=‘a1‘, shape=[1], initializer=tf.constant_initializer(1))
 8     a4 = tf.Variable(tf.random_normal(shape=[2,3], mean=0, stddev=1), name=‘a2‘)
 9
10 with tf.Session() as sess:
11     sess.run(tf.global_variables_initializer())
12 #    print (a1.name)
13     print (a2.name)
14 #    print (a3.name)
15     print (a4.name)
16
17 #输出:
18 ‘‘‘
19 V1/a2:0
20 V2/a2:0
21 ‘‘‘

接下来看看tf.Variable和tf.get_variable()的区别

在tensorflow中,tf.Variable和tf.get_variable()两个op分别用来创建变量。

tf.Variable()总是创建新的变量,返回一个variable,可以定义名字相同的变量,若给出的name已经存在,会自动修改name,生成个新的:

1 import tensorflow as tf
2 w_1 = tf.Variable(3,name="w_1")
3 w_2 = tf.Variable(1,name="w_1")
4 print (w_1.name)
5 print (w_2.name)
6 #输出
7 #w_1:0
8 #w_1_1:0

tf.get_variable()不可以定义名字相同的变量,tf.get_variable函数拥有一个变量检查机制,会检测已经存在的变量是否设置为共享变量,如果已经存在的变量没有设置为共享变量,TensorFlow 运行到第二个拥有相同名字的变量的时候,就会报错。
不同的变量之间不能有相同的名字,除非你定义了variable_scope,这样才可以有相同的名字。

1 import tensorflow as tf
2
3 w_1 = tf.get_variable(name="w_1",initializer=1)
4 w_2 = tf.get_variable(name="w_1",initializer=2)
5 #错误信息
6 #ValueError: Variable w_1 already exists, disallowed. Did
7 #you mean to set reuse=True in VarScope?

tf.get_variable一般和tf.variable_scope配合使用,用于在同一个的变量域中共享同一个变量。

如何在tensorflow中保存和加载模型呢?

构建网络中加入:saver = tf.train.Saver()

然后在session会话中:saver.save(sess, "./model/model.ckpt")

加载模型:

构建网络中需要和之前一样,然后在session会话中加载模型:

saver.restore(sess, "./model/model.ckpt")

然后和面试官讨论一些机器学习算法的问题,诸如LR和SVM的区别,随机森林和GBDT区别,xgboost以及最优化算法的原理等等,很快一个多小时就过去啦,感觉自己表现得不是太好,但是和面试官还是挺聊得来的,面试的最后,面试官问我对一些经典的数据结构熟悉不?我说还可以,然后他让我现场写一个单链表的逆序,很简单的问题,我却没写出来,我曾经看过java版本和c版本的数据结构与算法,前不久也看过用python实现的数据结构与算法,但是这个时候我却卡住了。10分钟后面试官看我连这个简单的问题都没写出来,笑着对我说该不该给我第二轮技术面的机会,然后第一轮技术面就这样结束了,让我在旁边的椅子上稍等一下...

等的过程中,我拿出手机百度了下单链表的逆序如何实现,恍然大悟的同时也有点懊恼,参考网上的答案,实现如下:

循环反转单链表:

 1 #定义一个单链表节点
 2 class ListNode:
 3     def __init__(self,x):
 4         self.data = x
 5         self.next = None
 6
 7 def nonrecurse(head):              #循环的方法反转链表
 8     if head is None or head.next is None:
 9         return head
10     pre=None
11     cur=head
12     h=head
13     while cur:
14         h=cur
15         tmp=cur.next
16         cur.next=pre
17         pre=cur
18         cur=tmp
19     return h
20
21 head=ListNode(1)    #测试代码
22 p1=ListNode(2)      #建立链表1->2->3->4->None;
23 p2=ListNode(3)          #head->p1->p2->p3->None
24 p3=ListNode(4)
25 head.next=p1
26 p1.next=p2
27 p2.next=p3
28
29 p=nonrecurse(head)   #输出链表 4->3->2->1->None
30 while p:
31     print (p.data)
32     p=p.next

递归实现单链表反转:

 1 class ListNode:
 2     def __init__(self,x):
 3         self.val=x;
 4         self.next=None;
 5
 6
 7 def recurse(head,newhead):    #递归,head为原链表的头结点,newhead为反转后链表的头结点
 8     if head is None:
 9         return ;
10     if head.next is None:
11         newhead=head;
12     else :
13         newhead=recurse(head.next,newhead);
14         head.next.next=head;
15         head.next=None;
16     return newhead;
17
18 head=ListNode(1);               #测试代码
19 p1=ListNode(2);                 # 建立链表1->2->3->4->None
20 p2=ListNode(3);
21 p3=ListNode(4);
22 head.next=p1;
23 p1.next=p2;
24 p2.next=p3;
25 newhead=None;
26 p=recurse(head,newhead);           #输出链表4->3->2->1->None
27 while p:
28     print (p.val)
29     p=p.next;

接下来就是技术第二面了,不说了,说多了都是泪...

参考:

  1. 窥探算法之美妙---寻找数组中最小的K个数&python中巧用最大堆
  2. 程序员编程艺术:第三章、寻找最小的k个数
  3. tf.variable_scope和tf.name_scope的用法
  4. tf.Variable()与tf.get_variable()与不同之处
  5. TensorFlow模型保存和加载方法
  6. 单链表反转python实现

原文地址:https://www.cnblogs.com/aoanng/p/9581612.html

时间: 2024-07-31 14:34:39

第一篇博客--记面陌陌科技计算机视觉算法工程师被刷经历(附面试过程中被问倒的一些算法题分析)的相关文章

第一篇博客——基于数组的优先队列(java版)

看过园子里和CSND上那么多大牛精彩的博客后,早就按捺不住想亲手写上几篇.奈何每次坐在电脑前准备敲字的时候,立马赶到浑身不自在,无从下手.实在是因为自高考之后,大学以来,本人几乎就再没动笔写过一篇文字,写作水平退化实在严重.今天鼓起勇气开始写作博客,一方面希望通过多写慢慢地找回写作的感觉,一方面也希望通过博客和大家多多交流,共同进步. 既然是第一次试手,就写个简单易懂的内容——优先队列. 话不多说,先上代码. 1 /** 2 * @author Mr Left 3 * @version 1.0

我的第一篇博客记录我的IOS生涯

这是我的第一篇博客,只为了记录我在IOS开发旅程中得成长之路,一直觉得能发表几百篇的都是大牛,一直都是我崇拜的对象,我希望现在的一小步能成为将来的一大步,坚持每天发表一篇博文,养成一个良好的每天学习新知识的习惯,希望多年以后点开这第一篇博客能感叹当初的自己是多么的幼稚,但也感谢当年的坚持!come on!活着就是为了改变世界!

第一篇博客:一个双色球游戏 、以及个人介绍

一.双色球小游戏 双色球类(6红1蓝): 1 package top.liaoyingpeng.bean; 2 3 import java.util.Arrays; 4 5 public class Balls { 6 private int[] red = new int[6]; 7 private int blue; 8 private BallBox makeBy; 9 10 // 全机器生成 11 protected Balls(BallBox bx) { 12 makeBy = bx;

第一篇博客,希望是一个好的开始

博客园"占座位" 因为学习专业的原因,经常浏览博客学习和解惑.在前辈们的作品中学到了很多,也感受到了很多.通过博客的方式去分享知识和记录自己的成长.这种积极向上的,乐于分享的精神,让我很受鼓舞.我时刻准备着加入这一大军的行列.于是我就注册了个账号,呵呵. 第一篇博客 经常会突发奇想的计划着怎么怎么样,却在行动的时候虎头蛇尾了,很难坚持作下去.这或许是很多人都存在的问题.在别人博客上看到:"或许写博客是一个不错的方法".通过博客,经常来记录一下自己的学习,分享一下自己

第一篇博客《import tensorflow的问题解决》

在jupyter notebook中死活无法import jupyter 搞了很久,最后是解决了,过程如下: 1)安装anaconda3 4.2.0版本 2)解决activate tensorflow问题 3)解决import tensorflow问题 以下就两个方面进行详细记录: 1)activate tensorflow失败问题 https://www.cnblogs.com/lvsling/p/8672404.html 根据以上文章操作,解决了激活tensorflow的问题,上述文章我是只

第一篇博客当测试,codeigniter遇到坑了

第一篇博客当测试,codeigniter遇到坑了,耽误了1天时间,才弄明白 如果你发现无论输入什么 URL 都只显示默认页面的话,那么可能是你的服务器不支持 PATH_INFO 变量,该变量用来提供搜索引擎友好的 URL . 解决这个问题的第一步是打开 application/config/config.php 文件, 找到 URI Protocol 信息,根据注释提示,该值可以有几种不同的设置方式, 你可以逐个尝试一下. 如果还是不起作用,你需要让 CodeIgniter 强制在你的 URL

Hello World —— 我的第一篇博客

2019.2.22,我开始写起了属于我的博客,没有什么花里胡哨的想法,目的很简单:就想将自己在学习过程中的每一阶段所学到的知识.所遇到的问题用文字的方式记录下来,方便以后回头看看的时候,能知道自己在这个行业里处于一个什么阶段.好了,废话不多说,第一篇博客,不谈技术,先聊聊自己的一些想法吧.我最喜欢的一件事就是在晚上,一个人静静地坐在那里,看着满天繁星,思考着自己的问题,那时候很安静,很孤独.不过我喜欢孤独.技术也是这样,学技术的道路是孤独,是寂寞的,你要靠你自己一点点提升,难受了你要靠你自己挺过

第一篇博客------致自己

我为什么在博客园开博? 2019年5月10日,我在博客园写下自己的第一篇博客,用以记录和促进自己的技术成长.同时也希望自己的记录能给需要的人带来点启示. 我的博客将由以下几个分类的文章组成: 编程语言:到目前为止开发主要用Java,涉猎Java.Python.JQuery.js等.这个分类将会分享一些编程过程中理解到的小trick.值得借鉴的代码等. Lunix操作:学号Lunix操作真的是会收益终身的事情,而且装逼属性Max.这个模块主要会分享一些Lunix相关的技巧. 数据库:数据库是开发过

我的第一篇博客《Hello World》

Hello World! 大家好,这是我的第一篇博客. 作为一名程序员,为了保持优良的传统,所以就从Hello World 开始吧. 简单的介绍 关于名字 本人呢不太会起名字,试了好多个都被占用了,受一本小说影响,所以就起了这个名字. 同时呢,大家可以叫我铵静,做一个铵静的美男子哈. “高手有一颗寂寞的心,因为高手的造就本就是用寂寞堆积而成” —— <高手寂寞> 作者:兰帝魅晨 我的日常 目前主要从事Java EE企业级应用开发相关工作,业余时对网络安全比较感兴趣.周末主要是看看书,听听歌,偶