今日头条财经部门后台研发实习生面试

本文记录了作者申请今日头条财经部门后台研发实习生的面试经历,包括职位投递、一面的二叉树转双向链表算法题、进程与线程的区别、TCP三次握手等基础知识问答,以及二面的项目讲解和算法题,最后是三面的轻松聊天环节。面试整体偏简单,适合实习生水平。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

职位投递

原文地址:https://keep-learning.top/2018/03/13/今日头条财经部门后台研发实习生面试/

我是2018年春节前投的这个职位,在今日头条的 招聘官网 投递的简历。简历交上去第二天头条就打来了电话,当时还蛮感慨,头条HR在春节前一天居然也这么兢兢业业。最终把面试时间定到了2月26号。

面试的地点在知春里地铁站附近的中国卫星通信大厦。头条占了大厦的好几层楼,有专门的一层楼是用来面试的,每个面试者都会在一个独立的单间进行面试。此外面试间里还有一个空气净化器,感觉头条还是蛮重视员工身体健康的。

一面

第一面的面试官是一个跟我年纪差不多的小哥。上来直截了当让我先做道算法题。

题目一:把一颗二叉树中序遍历转换成双向链表

我当时把题意理解成,创建一个新的链表,不用原来树的节点。后来想了想他的意思应该是不创建新的节点, 而是把二叉树的节点当做双向链表的节点,分别把left和right指针当做双向链表的next和prev指针。面试官要求在白板上写,我觉得题目比较简单,因此要求在电脑上用vim直接写出来。

以下是我当时面试时做出的解答。

// g++ -std=c++11 mianshi.cpp && ./a.out
#include <iostream>
using namespace std;
struct ListNode {
    ListNode *prev, *next;
    int val;
    ListNode(ListNode* p, ListNode* n, int val): prev(p), next(n), val(val){}
};

struct TreeNode {
    TreeNode *left, *right;
    int val;
    TreeNode(TreeNode* l, TreeNode* r, int val): left(l), right(r), val(val){}
};

pair<ListNode*, ListNode*> helper(TreeNode *root) {
    if (!root) {
        return make_pair(nullptr, nullptr);
    }
    auto pl = helper(root->left);
    auto pr = helper(root->right);

    auto head = pl.first;
    auto tail = pr.second;

    auto curNode = new ListNode(nullptr, nullptr, root->val);

    if (head) {
        curNode->prev = pl.second;
        pl.second->next = curNode;
    }

    if (tail) {
        curNode->next = pr.first;
        pr.first->prev = curNode;
    }

    return make_pair(head ? head : curNode, tail ? tail : curNode);
}

ListNode* getDoubleLinkedList(TreeNode *root) {
    return helper(root).first;
}

int main() {
    TreeNode left2(nullptr, nullptr, 4);
    TreeNode right2(nullptr, nullptr, 8);
    TreeNode left3(nullptr, nullptr, 12);
    TreeNode right3(nullptr, nullptr, 16);
    TreeNode left(&left2, &right2, 6);
    TreeNode right(&left3, &right3, 14);
    TreeNode root(&left, &right, 10);

    auto res = getDoubleLinkedList(&root);

    for (auto h = res; h; h = h->next) {
        cout << h << ' ' << h->val << endl;
    }
}

如果采用第二种理解的话,应该是如下解法。

pair<TreeNode*, TreeNode*> helperInplace(TreeNode* root) {
// return the list head and tail
    if (!root) {
        return make_pair(nullptr, nullptr);
    }
    auto pl = helperInplace(root->left);
    if (pl.second) pl.second->right = root;
    root->left = pl.second;
    auto pr =  helperInplace(root->right);
    root->right = pr.first;
    if (pr.first) pr.first->left = root;
    return make_pair(pl.first ? pl.first : root, pr.second ? pr.second : root);
}

TreeNode* getDoubleLinkedListInplace(TreeNode *root) {
    auto tail = helperInplace(root);
    return tail.first;
}
/* 遍历
    auto head = getDoubleLinkedListInplace(&root);
    for (auto h = head; h ; h = h->right) {
        cout << h->val << endl;
    }
*/

基础知识

因为我的简历里写到我之前做过一个小型 OS Kernel,此外也有少量计算机网络的问题。

Q:进程与线程的区。

A: 主要区别有两点,一是地址空间的区别,不同进程地址空间独立,同一进程的线程共享地址空间;二是资源管理的区别,相同进程的线程可以共享一部分进程的资源。并且资源通常以进程的单位分配。


Q: 简述x86中逻辑地址,线性地址和物理地址的区别。简述页式管理的原理。

A: 线性地址=逻辑地址+段基址,物理地址=页表中查线性地址。页式管理就是把地址分成三部分(x86二级页表),前10位确定页目录项,中间10位确定页表项,后面12位是offset,共同确定一个物理地址。


Q: 交换空间的作用,如果内存足够大,会用到交换空间吗。

A: 物理内存不够用时,可以把一部分页面换到磁盘中,这样,多个进程使用的内存总量可以超过物理内存的容量。第二个问题我不太了解,我之前在linux中做过实验,发现物理内存够用的时候似乎并不会占用交换空间。面试官告诉我说,其实交换空间即使会被利用,并且可以设置内核参数改变具体的行为。


Q: 简述调用open函数后,系统处理的过程。

A: open是libc的函数,内部会调用open syscall,并陷入内核态。内核根据路径,在目录树中查找对应的node。该过程中会用到VFS的接口。(这个问题我回答的不是很好,具体的概念还是了解的不太透彻)


Q: 简述TCP的三次握手和四次挥手。说明三次握手过程中,通信双方状态的变化。

A: 分别是SYN,SYN+ACK,ACK,最终确定链接。四次挥手是FIN,ACK,FIN,ACK。释放连接前需要等到TIME_WAIT。第二个问题当时我没太理解,后来想了想其实面试官想问的是TCP状态机。这个网上一搜就有。

结尾

结尾的时候面试官又随便问了一些问题。比如问我会不会Shell,我说懂一点,他就让我用shell命令实现一个Group By Field的功能,简单来说就是把

A 1
B 2
C 3
A 2
B 4

转换成

A 3
B 6
C 3

我说用awk或者写shell脚本都能实现,但是我得查文档,他就再没人我写。

然后他说要在面试系统里填些东西,就问我会什么shell命令,我就随便说了些比如find, grep, awk, chown, usermod之类的,提到find的时候他还问如何查找文件名以abc结尾的文件,回答曰find . -name ‘*abc’.,之后一面就结束了。

二面

一面面试官离开之后大概十来分钟,二面面试官就来了。二面的面试官似乎是部门中某个组的领导,相比上一个面试官要年长一些。
二面主要是两部分,下面分别来介绍。

第一部分,讲解项目

这一面主要询问了我所做的一个项目, KMR,这个项目是用Go写的基于Kubernetes的MapReduce框架。

面试官首先让我解释了一下什么是MapReduce。以及为什么这个编程模型实现了并行。

我的回答如下:

数据文件 =》Map =》list(k, v) with duplicated key => 框架根据k来sort这个list,并把相同k的v放到一起 => list(k, list(v)) => Reduce => list(k, v) with unique key

其中用户提供的是Map和Reduce函数,框架会在多个Node上运行用户定义的Map和Reduce函数,并实现数据的排序和存储。

Map和Reduce的对应关系如下图所示。

MapReduce图示

每个Mapper生成与Reducer数目相等的数据文件,每个Reducer从所有Mapper处读取与其相对应的数据文件。为了保证每个Mapper处生成的对应于某个Reducer的数据块中的Key范围一致,使用了Hash并对Reducer个数取模的方法进行shuffle。

至于Sort的过程,是先用快排生成比较小的FlushOut文件,然后使用归并排序把这些小的有序的文件合并,生成一个大的有序的数据文件。

底层的分布式文件系统则使用Ceph实现,任务的调度基于Kubernetes的Job实现。

关于MapReduce的详细讨论可以参考谷歌的论文。

第二部分,算法

这次问的算法仍然是比较简单的。给一颗二叉树,树的每个节点的值是一个0-9的个位整数,这样,从根节点到所有叶子节点会构成很多个十进制整数,要求算出所有这些整数的和。

这题面试官也同意我直接用vim写。

解答如下:

// g++ -std=c++11 mianshi.cpp && ./a.out
#include <iostream>
#include <vector>
using namespace std;
struct TreeNode {
    TreeNode *left, *right;
    int val;
    TreeNode(TreeNode* l, TreeNode* r, int val): left(l), right(r), val(val){}
};

vector<char> s;

uint64_t getStackNum() {
    int res = 0;
    for (auto num : s) {
        res *= 10;
        res += num;
    }
    cout << res << endl;
    return res;
}

void helper(TreeNode* root, uint64_t &sum) {
    if (!root) {
        return;
    }

    s.push_back(root->val);
    if (!root->left && !root->right) {
        sum += getStackNum();
        s.pop_back();
        return;
    }

    helper(root->left, sum);
    helper(root->right, sum);
    s.pop_back();
}

uint64_t getSum(TreeNode* root) {
    uint64_t sum = 0;
    helper(root, sum);
    return sum;
}
int main() {
    TreeNode left2(nullptr, nullptr, 4);
    TreeNode right2(nullptr, nullptr, 8);
    //TreeNode left3(nullptr, nullptr, 12);
    //TreeNode right3(nullptr, nullptr, 16);
    TreeNode left(&left2, &right2, 6);
    //TreeNode right(&left3, &right3, 14);
    TreeNode root(&left, nullptr, 2);

    cout << getSum(nullptr) << endl;
}

写完之后,这一面就结束了。

三面

二面结束后已经到中午饭点了,二面面试官带我去吃了头条的食堂,真的是人山人海= =。

吃完饭后又等了二十来分钟,三面面试官就来了。从他负责的工作来看,这个面试官好像级别更高一点。

这一面其实没有严肃的技术问题了,大概就是聊了一下职业规划、对流行技术的掌握等,并且讲了一下我来头条之后主要做的事。

这一面感觉就是瞎聊,表现出真实的自我就好。其中提到的一些流行技术,比如etcd,Kubernetes,thrift等,我并没有深入了解,也只是停留在用过的层面,但是面试官关注的点应该就是在你是否对新技术有学习的意识。

聊了大概半个小时这一面就结束了。面试官走时还忘记把我送走,干等了十几分钟,HR打来电话,告诉我可以走了,我才离开。

总结

面试总体来时还是比较偏简单的,毕竟实习生嘛。面试结束后第三天HR就来了电话说可以给Offer了,待遇也是头条的实习生批发价了。

头条给实习生开的工资还是蛮高的,至于房补,虽然听着很美好,但是实际操作起来会发现其实满坑的,房补还催生了所谓的“头条房”= =,这个可以在网上查到,就不细说了。

祝大家面试成功吧。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值