尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

腾讯2016研发工程师笔试题解析:从基础到算法的核心考点

腾讯2016研发工程师笔试题解析:从基础到算法的核心考点 每年腾讯研发工程师笔试一出总能炸出一堆“面试官到底想考什么”的讨论。2016年的笔试题三放到今天看依然值得刷不是因为题有多难而是它把研发岗最核心的基本功全串起来了数据结构、算法、操作系统、C/C 语言细节甚至还有不少选择题专门用来筛掉基础不扎实的简历党。这篇文章我就顺着这套题的整体脉络把高频考点、解题思路、易错点全部拆开讲。不管你是准备校招、社招还是单纯想看大厂笔试到底什么画风这套题都值得认真过一遍。1. 这套笔试题到底在考什么先看整体面1.1 题型分布与考察维度腾讯2016研发工程师笔试题三整体上延续了当时大厂笔试的典型结构选择题单选多选为主配合少量编程题。考察维度基本固定在四个方向考察方向常见题型占比感受数据结构与算法选择题、编程题约40%C/C 语言基础选择题、改错题约25%操作系统选择题约15%计算机网络选择题约15%其他数据库、设计模式选择题约5%这个分布不是巧合。腾讯的研发岗笔试向来不考偏题怪题它更看重的是“一个合格工程师在写代码时最常打交道的知识”。你说链表重不重要重要。你说 TCP 握手考不考几乎每年都有。这些知识点看起来基础但恰恰是大学里最容易“考完就忘”的部分。1.2 为什么大厂偏爱这些基础考点很多刚准备笔试的同学会有一个误区以为大厂笔试会像竞赛题一样全是复杂算法。实际上腾讯这套题透露出来的信号很明确——它筛选的不是“竞赛选手”而是“基础扎实、能干活的人”。我举个很直接的例子选择题里经常出现“以下关于指针的说法正确的是”。这类题不难但区分度极高。能把指针、数组、内存布局讲清楚的人写代码时大概率不会犯低级越界错误而只会背语法的人在这种题上基本一选一个错。另外这套题的编程题往往不要求你用多高深的算法而是考察你能否把思路转化成正确、健壮的代码。边界条件、空指针判断、复杂度估算这些才是真正拉开分数的地方。2. 数据结构与算法大题详解2.1 链表与指针高频中的高频链表相关题目在腾讯笔试里出现频率极高2016这道三也不例外。常见考法有三类链表反转、环检测、删除指定节点。每一种都有固定的套路但每个套路里都藏着不少坑。先说链表反转。最基本的迭代写法是三个指针 prev、cur、next 不断推进// 定义单链表节点 struct ListNode { int val; ListNode* next; ListNode(int x) : val(x), next(nullptr) {} }; ListNode* reverseList(ListNode* head) { ListNode* prev nullptr; ListNode* cur head; while (cur ! nullptr) { ListNode* next cur-next; // 先保存下一个节点 cur-next prev; // 反转指针 prev cur; // 前驱后移 cur next; // 当前节点后移 } return prev; }这个代码看起来简单但笔试时最容易错的点是“保存 next 节点”这一步。很多人写着写着就cur-next prev; cur cur-next;直接把链表搞断了。我建议你每次写链表操作前心里默念一句话修改 next 之前先确认下一个节点有没有保存。环检测的经典做法是快慢指针。快指针每次走两步慢指针每次走一步如果存在环两指针必然相遇。这个算法的正确性证明其实很有意思假设环外长度为 a环内长度为 b快慢指针相遇时慢指针走了 s 步快指针走了 2s 步二者相差 s 步而 s 必然是环长 b 的整数倍。把这个推导过程记住面试官追问的时候你就能底气十足地答上来。2.2 二叉树遍历与递归套路二叉树相关的题目在笔试题里通常是“给两个遍历序列求第三个遍历序列”或者“求树的深度、宽度”。这类题看着变化多其实核心就是两种能力递归遍历的熟练掌握、以及通过遍历序列还原树结构的能力。以前序 中序还原二叉树为例思路核心是前序的第一个节点一定是根节点在中序里找到这个根节点左边是左子树右边是右子树然后递归处理。TreeNode* buildTree(vectorint preorder, vectorint inorder, int preLeft, int preRight, int inLeft, int inRight) { if (preLeft preRight) return nullptr; TreeNode* root new TreeNode(preorder[preLeft]); // 在中序序列中找到根节点下标 int rootIdx inLeft; while (inorder[rootIdx] ! root-val) rootIdx; int leftSize rootIdx - inLeft; root-left buildTree(preorder, inorder, preLeft 1, preLeft leftSize, inLeft, rootIdx - 1); root-right buildTree(preorder, inorder, preLeft leftSize 1, preRight, rootIdx 1, inRight); return root; }这里有个细节很多人会忽略递归实现一定要明确递归终止条件否则栈溢出就是一瞬间的事。笔试的时候时间紧我习惯先把终止条件写在最前面再写递归逻辑防止自己写着写着忘了边界。另外层序遍历按层输出是queue 循环的经典组合每轮循环前先记录当前队列长度这样就能把每一层分开处理。这套思路在“之字形打印二叉树”“求每层最大值”等变形题里都能直接用。2.3 动态规划从暴力递归到状态转移动态规划是笔试题里的分水岭。腾讯这套题里如果出现 DP大概率不是那种特别绕的题而是“最长公共子序列”“最大连续子数组和”“编辑距离”这类经典模型。以最大连续子数组和为例状态转移方程非常简洁dp[i] max(nums[i], dp[i-1] nums[i])。意思是以第 i 个元素结尾的最大子数组和要么是只有自己要么是前面的最优解加上自己。int maxSubArray(vectorint nums) { int dp nums[0]; int res nums[0]; for (int i 1; i nums.size(); i) { dp max(nums[i], dp nums[i]); res max(res, dp); } return res; }做 DP 题最大的坑不是方程写不出来而是初始状态定义不清。很多人一上来先写循环结果dp[0]到底表示什么都没想明白后面全乱套。我自己的习惯是先花两分钟在草稿纸上定义状态回答三个问题——状态表示什么初始值是什么转移方程是什么三个问题都回答了再动笔写代码。2.4 排序与 TopK 问题排序在选择题里一般会问“快排最坏时间复杂度”“哪些排序是稳定的”这种记忆类问题编程题里则更喜欢考 TopK。拿到 TopK如果数据量不大直接priority_queue维护大小为 K 的小顶堆如果数据量大到内存放不下就得用快排的 partition 思想。// 快速选择求数组中第 K 大的元素 int quickSelect(vectorint nums, int left, int right, int k) { int pivot nums[left]; int i left, j right; while (i j) { while (i j nums[j] pivot) j--; nums[i] nums[j]; while (i j nums[i] pivot) i; nums[j] nums[i]; } nums[i] pivot; if (i k) return nums[i]; else if (i k) return quickSelect(nums, i 1, right, k); else return quickSelect(nums, left, i - 1, k); }注意快速选择的时间复杂度是期望 O(n)最坏退化到 O(n²)。笔试时如果能说出“可以通过随机化选择基准来避免最坏情况”面试官会对你另眼相看。3. 操作系统与计算机网络选择题重灾区3.1 进程线程的必考点操作系统在笔试题里通常是选择题的重灾区。2016这套题的进程线程部分主要围绕几个经典概念展开进程与线程的区别、上下文切换开销、死锁的四个必要条件、生产者消费者模型。有一个高频陷阱题问“以下哪个是所有线程共享的”。答案是“地址空间”和“打开的文件”。线程共享进程的地址空间但每个线程有自己的栈和寄存器上下文。这个知识点如果不理解靠死记硬背很容易被选项迷惑。建议你在准备时画一张进程地址空间图把代码段、数据段、堆、栈的位置关系弄清楚很多相关题目就都能顺带解决。死锁这块“互斥、持有并等待、不可剥夺、循环等待”四个必要条件一定要能默写。笔试选择题里经常给一个场景问你“破坏的是哪个条件”比如一次性分配所有资源就是破坏了“持有并等待”。3.2 TCP 状态机与握手细节网络部分必考 TCP。握手、挥手的状态切换、TIME_WAIT 存在的原因这三个几乎是标配。TIME_WAIT 这道题是经典中的经典主动关闭连接的一方在收到 FIN 之后进入 TIME_WAIT等待 2MSL 后才真正关闭。原因有两个一是确保最后一个 ACK 能让对方收到如果 ACK 丢了可以重发二是让旧连接上的所有报文段在网络中自然消失防止影响新连接。笔试题还会问“为什么建立连接只要三次握手而不是两次”。核心原因是三次握手可以确认双方的收发能力都正常。第一次握手让服务端确认客户端发送能力正常第二次让客户端确认自己的发送、服务端的接收都正常第三次让服务端确认客户端的接收能力正常。如果没有第三次服务端永远无法确认客户端的接收能力。3.3 内存管理与进程地址空间内存管理的考点通常集中在虚拟内存、分页和段页式存储。选择题喜欢考“虚拟内存的目的是什么”标准答案是“扩大逻辑内存让进程感觉自己拥有连续的大地址空间同时实现内存隔离”。这里有一个很容易混淆的知识点虚拟内存的大小受什么限制答案是地址总线位数。比如 32 位系统虚拟地址空间最大是 4GB而不是由物理内存大小决定。题目里如果问“4GB 物理内存的机器32 位进程能使用多少虚拟地址空间”答案是 4GB不要被物理内存的数值带偏。4. C/C 语言基础与内存陷阱4.1 数组与指针的辨析数组与指针的题目是腾讯笔试选择题的灵魂。几乎每套研发笔试题都会出现一题“sizeof 一个数组”和“sizeof 一个指针”的区别是多少。char str[] hello; char* p str; sizeof(str); // 6包含 \0 sizeof(p); // 864位系统或 432位系统这里的核心是数组名在大部分表达式中会退化为指向首元素的指针但在sizeof和取地址中不会退化。这个规则一定要记牢。另外还有个经典陷阱strlen(str)是 5因为strlen是运行时计算的它不会统计\0。还有一个容易踩的坑是数组作为函数参数时的退化void func(int arr[]) { sizeof(arr); // 这里 arr 已经退化成指针得到的是指针大小 }很多同学在函数里面用sizeof(arr) / sizeof(arr[0])想算数组长度结果算出来是错的。这个错误我见过太多人犯了笔试选择题也很爱考。4.2 内存分配与泄漏检测C/C 内存管理里new/delete与malloc/free的区别是高频题。标准答法malloc只分配内存不调用构造函数new不仅分配内存还会调用构造函数完成对象初始化。对应的free不会调用析构函数而delete会。内存泄漏的题目通常会给你一段代码让你找哪里泄漏了。常见的模式是函数中new了一个对象但提前 return导致delete没执行。笔试中遇到这种题建议按“每一对 new/delete 是否成对出现”的方式逐行检查。我在实际开发中排查内存泄漏时还是习惯用工具辅助比如 Valgrind 或者 AddressSanitizer。但笔试时不会给你工具只能靠肉眼扫代码。你可以刻意训练自己遇到new就立刻在草稿纸上记一笔等看到对应的delete再划掉这样能有效避免漏判。4.3 const、static、volatile 的高频考法这三个关键字在选择题里出现的频率也是居高不下。我整理了一个表格方便对照关键字核心作用经典考题const修饰变量只读修饰成员函数表示不修改成员变量const int* p与int* const p的区别static修饰局部变量延长生命周期修饰全局变量限制作用域修饰成员变量为类共享static 局部变量是否存放在静态区volatile告诉编译器该变量可能被外部修改禁止优化中断服务函数和主循环共享的变量是否要加 volatileconst int* p和int* const p这道题每年都能难倒一片人。口诀是const 修饰谁谁就不能变。const int* p是“指向 const int 的指针”指针本身可以变指向的值不能改int* const p是“指向 int 的 const 指针”指针本身不能变指向的值可以改。我推荐一个更稳的理解方式从右往左读遇到const就标记它左边最近的那个类型。5. 模拟实战一道编程题从审题到 AC5.1 典型题目举例编程题我拿一道这套试卷里极具代表性的题来说考点综合了字符串处理和双指针技巧给定一个字符串以单词为单位反转句子顺序。比如输入I am a developer输出developer a am I。这个题有两种常见思路。第一种是先用空格分割单词存到 vector再逆序拼接时间复杂度 O(n)空间复杂度 O(n)。第二种更巧妙先反转整个字符串再反转每个单词。两种都能 AC但第二种不依赖split函数在不同语言里都通用。5.2 审题与边界分析拿到这道题第一步不是写代码而是列边界条件输入为空字符串怎么办输入只包含空格怎么办多个连续空格算不算单词分隔符字符串开头或结尾有空格怎么处理这些边界条件在题目描述里不一定明说。笔试时如果题目没限定你就需要自己判断最稳妥的处理方式。我个人建议在代码里统一处理用双指针扫描跳过连续空格这样不管输入多乱都能保持行为一致。string reverseWords(string s) { int n s.length(); // 反转整个字符串 reverse(s.begin(), s.end()); int i 0; for (int j 0; j n; j) { if (s[j] ! ) { if (i ! 0) s[i] ; // 单词之间补一个空格 int start i; while (j n s[j] ! ) { s[i] s[j]; } reverse(s.begin() start, s.begin() i); // 反转当前单词 } } s.resize(i); return s; }仔细看这个解法它直接在原字符串上操作同时完成了“去除多余空格”和“反转单词”两件事空间复杂度降到 O(1)。笔试的时候如果能在实现基本功能后主动提到“可以优化为 O(1) 空间”会比只交一份能跑的代码得分高很多。5.3 复杂度估算与优化思路写完基础版本后一定要养成检查时间复杂度的习惯。这道题的时间复杂度是 O(n)只需要两次遍历一次反转整个字符串一次逐单词反转。空间复杂度根据实现方式不同从 O(n) 到 O(1) 都有可能。笔试里时间紧但复杂度分析是拿高分的关键。我建议在编程题最后加一行注释写上时间和空间复杂度一方面方便自己检查算法是否最优另一方面让面试官一眼看出你有复杂度意识。6. 备考避坑指南与常见问题6.1 常见问题速查表我把这套题里最容易出错、也最常被问到的知识点整理成一张速查表考前过一遍很有用问题易错点正确答案数组作为函数参数传递后 sizeof 结果误以为还能得到数组长度得到的是指针大小const int* p与int* const p区别混淆 const 修饰对象前者不能改指向值后者不能改指针本身进程与线程共享什么误以为栈也共享共享地址空间、文件、全局变量不共享栈和寄存器TCP 为什么要 TIME_WAIT答不出 2MSL 原因保证 ACK 可达 让旧报文消失快排最坏时间复杂度只记得平均 O(n log n)最坏 O(n²)发生在每次 partition 极不平衡时虚拟内存大小限制因素误以为由物理内存决定由地址总线位数决定new 与 malloc 核心区别只说“一个 C 一个 C”是否调用构造函数/析构函数这张表里的每一项都是选择题高频考点。我建议你不仅背答案还要能把每一条的“为什么”讲清楚。平时练题的时候遇到一个不确定的选项就去把相关知识点彻底弄清楚而不是只记一个正确选项。6.2 刷题与复习路线建议如果你准备时间充裕我建议按“三轮式”复习第一轮快速过语言基础把 C/C 的指针、内存、关键字反复刷到滚瓜烂熟第二轮集中刷数据结构和算法题分类刷链表、树、DP、排序第三轮做真题模拟严格计时训练考场节奏。有个很实用的技巧每次刷完一套题把错题按知识点归入一个文档多轮复习时只看文档。我当年准备笔试时就是这么做的效果立竿见影。你不需要把每道题的代码都背下来但一定要把每个知识点的“错因”记录清楚比如“哦我是因为不知道数组做参数会退化才错的”这种记录比题目本身更有价值。6.3 笔试现场的时间分配技巧说一个很多人容易忽略的事笔试时间分配。腾讯的笔试题量不算小选择题编程题加起来往往需要 1.5 到 2 小时。我见过太多同学在前面选择题上死磕一道有疑问的题结果后面的编程题没时间写。我的建议是选择题不确定就先标记跳过把能拿的分全部拿到。编程题如果卡壳超过 15 分钟先停下来重新审题梳理输入输出和边界条件把能写的框架先写上。很多时候代码写了一半思路就通了。还有一个小技巧如果编程题实在没时间调通也要把解题思路和关键代码框架写上去。大厂笔试的人工复筛环节通常会看你的思路哪怕运行不通“思路清晰”也会得到一部分分数。最后再分享一点个人体会我自己的感觉是像腾讯 2016 研发工程师笔试题三这样的老题放到今天依然有很高的复习价值。它的难度不是那种“劝退型”的而是处处都在提醒你基础不牢地动山摇。真正能通过的候选人不见得算法竞赛成绩多亮眼但一定是对指针、内存、递归、TCP 这些底层概念有自己清晰理解的人。所以答题的时候不要只满足于“做过一遍”而是每道题都追问一句“如果我是出题人我会在哪个位置设计陷阱”。顺着这个思路去准备收获会比你想象的大很多。
返回列表