1. 先聊聊这套题背后的逻辑
恒生电子这名字,放到2015年的校招圈子里,基本等同于“金融IT的黄埔军校”。那几年券商、基金、银行集体搞系统升级,恒生在国内证券交易系统这块的份额摆在那儿,对计算机专业的应届生来说,进恒生做开发,最直观的吸引力就是——你写的代码会跑在真实的交易链路上。这种“代码直接决定真金白银”的压力感,比做普通业务系统刺激得多。
但我得先说句实话:恒生的笔试题目并不难,或者说,难的点不在“偏”和“怪”,而在“基础扎不扎实”。2015年秋招这套开发类笔试题,我后来在好几个技术群里都看到有人讨论,大家一致的感受是——题目覆盖面很广,但深度并不夸张,C语言、数据结构、操作系统、网络、数据库全都有涉及,题型也比较常规,选择题、填空题、简答题、编程题一应俱全。
为什么金融IT公司会出这种风格的卷子?这得从业务角度理解。交易系统对稳定性和性能的要求极高,一个内存泄漏、一处指针越界、一个并发问题,放在普通业务系统里可能只是报错重启,放在交易系统里就是生产事故。所以恒生这类公司招人,最看重的不是你掌握多少新框架,而是你的计算机基础扎不扎实,能不能写出内存安全、逻辑严谨的代码。这套笔试题本质上是按照“能不能胜任生产级系统开发”这个标准来设计的。
这套题适合谁看?如果你是正在准备校招的技术学生,或者工作后想往金融IT方向转的开发,都可以拿这套题当一面镜子,照一照自己的基础能力。我下面会把整套题的考察逻辑、高频考点、实战解析以及我踩过的坑全部展开来说。
2. 题型分布与整体拆解:一张卷子背后的考察逻辑
2.1 从题型看恒生的用人标准
先说整体结构。2015年这套开发类笔试题大致分为四块:选择题、填空题、简答题、编程题。选择题和填空题覆盖的知识点高度集中,基本围绕C语言语法、指针与内存、数据结构、操作系统基础、计算机网络这几个方向。简答题则以概念阐述为主,比如进程与线程的区别、TCP三次握手的过程这类。编程题通常是两道,一道偏算法,一道偏字符串或链表操作。
把这个结构拆开看,能明显感受到恒生的用人标准:首先是“能不能写C”——金融IT的老系统大量基于C/C++构建,哪怕是后来转向Java的团队,面试时也默认你懂C;其次是“懂不懂系统”——进程、线程、内存、网络,这些是分布式交易系统的基础构件;最后是“有没有算法思维”——编程题不考难题怪题,但要求你在白纸上写出能跑、能处理边界情况的代码。
2.2 和互联网公司笔试题的差异
2015年那会儿,互联网公司的笔试题已经开始卷算法了,动辄就是动态规划、图论、树形DP,一上来就是LeetCode中等偏上难度。恒生的题不太一样,它更偏“工程基础”,很多题目直接来自《C程序设计语言》的课后习题改编,或者《深入理解计算机系统》里的经典考点。
这不是说恒生的题简单,而是它的考察维度不同。互联网公司想筛出“算法能力强、能搞定高并发互联网场景”的人,恒生想筛出“基础扎实、代码严谨、能理解系统底层”的人。所以在备考策略上,如果你目标是恒生这类金融IT公司,刷LeetCode的优先级反而要往后放,先把C语言、数据结构、操作系统的教科书吃透更重要。我当时认识一个同学,算法题刷得飞起,但死在了一道结构体内存对齐的选择题上,非常可惜。
3. 选择题里的高频考点:C语言与数据结构是重头戏
3.1 指针与数组:最容易被绕进去的两兄弟
选择题里几乎必考指针和数组的关系。恒生2015年这套题里有这么一道,我印象很深:
char *p = "hello"; p[0] = 'H'; printf("%s", p);问这段代码会输出什么。答案是编译能过,但运行时报错。因为字符串字面量存放在只读数据段,试图通过指针修改它属于未定义行为。很多同学栽在这里是因为搞混了两个概念:char *p = "hello"和char p[] = "hello"。前者指向只读常量区,后者是在栈上拷贝了一份可修改的字符串。这个概念在金融系统开发里很关键,因为交易系统里大量使用配置字符串和协议报文,误修改只读区域会导致不可预知的行为。
还有一个经典考点是数组名和指针的区分。sizeof(arr)和sizeof(ptr)的结果完全不同,前者是整个数组的字节数,后者是指针本身的字节数。看似简单,但笔试时一紧张就容易写错。我建议遇到这类题,先在草稿纸上画出内存布局——栈、堆、全局区、常量区分别画出来,指针指向哪里、数组占用多大,一目了然。
3.2 结构体对齐与内存布局
结构体内存对齐是那年选择题里让人最意外的一道,后来也成为我面试别人时最爱问的一个点。题目大概是这样:
struct Test { char a; int b; char c; }; printf("%lu", sizeof(struct Test));在32位系统上答案是12,不是6。原因是编译器会按默认对齐规则在成员之间插入填充字节,char a后面要填充3个字节让int b对齐到4字节边界,char c后面再填充3个字节让整个结构体的大小是最大对齐数的整数倍。
我为什么说这道题很能筛选人?因为如果你只是背过“结构体会对齐”这个结论,而不理解对齐的本质是“以空间换时间”——CPU访问未对齐的内存可能需要两次内存访问——那你就很难解释为什么char a; int b; char c;是12字节,但char a; char c; int b;也是8字节(这里分别是12和8,前一个最大对齐数是4,后一个把两个char放一起后填充2字节即可)。恒生考这种题,就是看你对“代码运行时的真实样子”有没有感知。金融系统里,这些结构体往往直接对应网络报文的格式,内存布局错一位,整个报文解析就全错了。
3.3 排序与查找的复杂度陷阱
数据结构的选择题集中在排序和二叉树。比如问:以下哪个排序算法是不稳定的?选项里一般有冒泡、插入、归并、快排。正确答案是快排,因为快排的交换过程可能改变相同元素的相对顺序。这种题本身不难,但如果你想当然地认为“快排这么快,应该很稳定”,就掉坑里了。记稳定性的关键方法是记住三个不稳定的:快排、堆排、希尔排序,其他的常见排序基本都是稳定的。
二叉树这块,考得最多的是根据遍历序列反推二叉树。比如给前序遍历ABDEC和中序遍历DBEAC,问后序遍历是什么。这种题的解法是:前序的第一个节点是根节点,在中序里找到根节点的位置,左边是左子树、右边是右子树,然后递归处理。金融系统里二叉树用得不算多,但红黑树在交易引擎的订单簿里是核心数据结构,这也是为什么这类题目始终占据C位。
4. 填空题与简答题:基础概念的“说人话”版本
4.1 进程与线程:一道题看出你写没写过并发
简答题里必有一道“进程和线程的区别”。这种题看着简单,想拿满分不容易。很多同学答“进程是资源分配的最小单位,线程是CPU调度的最小单位”,这句话没错,但只给一半分。恒生的评分标准更看重你能不能结合场景说清楚:进程拥有独立的地址空间,一个进程崩了不影响另一个;线程共享进程的地址空间,线程之间通信成本低但同步复杂;创建进程的开销比创建线程大得多,因为要分配独立的地址空间和资源。
如果延伸一下,结合金融场景来答会更出彩。比如交易系统中,撮合引擎通常用多线程模型——因为线程共享内存,订单簿可以直接放在共享内存里,访问延迟低;但风控模块往往独立成进程,因为要隔离故障,避免风控崩溃拖垮整个交易链路。这种答案能让阅卷人看出你是真懂,而不是背了八股。
4.2 static、const、volatile:C语言三大修饰符的考点
恒生这套题里对C语言修饰符的考察非常细,填空和选择都有涉及。static的三个作用基本是必背的:修饰局部变量时,变量存储在静态区,生命周期延长到程序结束,但作用域不变;修饰全局变量时,限制作用域为本文件,避免命名冲突;修饰函数时同理,限定函数只能在当前文件内使用。这套题里有一道填空题,让你补全代码,题目故意在一个被多个文件引用的全局变量前加了static,然后问编译会不会报错——答案是不会报错,但其他文件就引用不到了,链接时会失败。
const和volatile也是高频点。const告诉编译器这个变量不应该被修改,但注意const int *p和int *const p的区别,前者是指针指向的内容不可变,后者是指针本身不可变。volatile则是告诉编译器这个变量可能被外部修改,不要优化掉对它的访问。在金融IT的嵌入式网关场景里,volatile常用于访问内存映射的硬件寄存器,因为寄存器的值会随时变化,如果编译器把它优化成从缓存读取,就会读到错误的值。
4.3 TCP三次握手:网络题里的常青树
网络部分的简答题几乎是固定套路:“描述TCP三次握手的过程,为什么需要三次?”三次握手本身好答:客户端发SYN,服务端回SYN+ACK,客户端再回ACK,连接建立。关键在于解释“为什么是三次而不是两次”——为了防止已失效的连接请求报文突然又传到服务端,导致服务端建立无效连接并浪费资源。
我当时额外加了一句:从序列号同步的角度看,三次握手本质上是双方各自确认自己的发送能力、接收能力和对方的收发能力都正常。第一次握手后服务端知道客户端能发,第二次握手后客户端知道服务端能收能发,第三次握手后服务端知道客户端能收,至此双方对通信能力达成共识。这种答法比单纯背流程更能体现理解深度。恒生做的是交易系统,网络通信的可靠性是命根子,TCP协议栈的细节自然会成为考察重点。
5. 编程题实战:从读题到AC的完整路径
5.1 链表反转:迭代与递归两种思路
编程题第一道大概率是链表相关题目,反转单链表是出现频率最高的一道。题目很简单:给定一个单链表,返回反转后的链表头节点。
当时大部分同学第一反应是用迭代法,三指针prev、curr、next原地反转:
struct ListNode* reverseList(struct ListNode* head) { struct ListNode *prev = NULL; struct ListNode *curr = head; while (curr != NULL) { struct ListNode *next = curr->next; curr->next = prev; prev = curr; curr = next; } return prev; }这道题有两个隐藏考点。第一是返回的必须是新的头节点,很多人原地反转后直接返回原来的head,结果输出的链表前半段是反转的后半段,后半段指向空,整个链表就断了。第二是空链表和单节点链表的边界条件,这两个情况代码里必须能正确处理。
如果笔试要求用递归实现,思路也简单:递归到链表末尾,返回末尾节点作为新头,回溯过程中把当前节点的next->next指向当前节点,再把当前节点的next置空。递归实现的代码非常简洁,但面试时最好把递归深度问题也提一下——链表很长时递归可能导致栈溢出,而迭代法没有这个问题。这种“额外说一句”往往是加分项。
5.2 字符串处理:最长回文子串的动态规划解法
第二道编程题我在2015年的卷子里见过变体,核心是“给定一个字符串,找出其中最长的回文子串”。这题用暴力解是O(n^3)的复杂度,笔试时间有限,最佳选择是动态规划,复杂度O(n^2)。
动态规划的递推关系是:dp[i][j]表示子串s[i...j]是否为回文。如果s[i] == s[j]且dp[i+1][j-1]为真,则dp[i][j]为真。边界条件是两个字符相邻的情况,s[i] == s[i+1]时dp[i][i+1]为真。注意遍历顺序要从子串长度从小到大,而不是简单地按i从小到大,否则会用到尚未计算的状态。
我当时写这个题的时候犯过一个低级错误:把dp表初始化成false,然后忘了把单个字符dp[i][i]初始化成true,导致所有长度为奇数的回文全部判错。这种细节问题在卷子上不容易发现,所以后来我建议所有刷题的人,DP题目先把边界条件写在注释里,再动手写代码。即使不能全部AC,清晰的边界处理也能让阅卷人觉得你思路严谨。
5.3 实战中的边界条件与复杂度权衡
编程题真正拉分的地方不在主流程,而在边界条件。比如反转链表里的空链表,最长回文里的空字符串和单字符串,二分查找里目标值不存在的情况,字符串操作里的中文字节问题等等。
那年有个同学考完跟我吐槽,说第二道编程题他明明写对了主体逻辑,但忘了处理输入字符串可能包含空格的情况——题目要求读入一行,他用了cin >> s,结果遇到空格就截断了。这种错误在笔试环境里特别容易犯,因为人紧张的时候会下意识选择最熟悉的输入方式,而不去仔细读题。我的习惯是拿到题先看输入格式,如果是整行字符串,优先用getline;如果涉及数组,先确认数组长度有没有给,是定长还是变长。
复杂度的权衡也是个得分点。某些题用O(n^2)的DP能过,用O(n^3)的暴力就超时,但如果你在注释里写清楚“此方法复杂度O(n^2),在n<=1000时可接受”,阅卷人会认为你有工程意识。反过来,如果你写出了O(n)的Manacher算法,但不小心写错了边界,那还不如稳定的O(n^2)DP拿分多。笔试求稳,不炫技。
6. 考场上常见的坑与排查技巧
6.1 时间分配:别在第一题死磕
这套题满分100分的话,选择题大概占40分,填空题20分,简答题20分,编程题20分。从分值分布就能看出来,选择题才是大头。但人的心理很奇怪,遇到一道选择题不确定,就总想把它想明白再往下走,结果15分钟耗在一道两分题上,后面的简答题和编程题时间紧张到字都写不工整。
我的建议是:选择题和填空题每道题最多3分钟,想不出来先标记跳过,等做完简答题和编程题再回头纠结。因为编程题一旦开始写,思路通畅的话20分钟能拿满20分,比死磕两道选择题值多了。时间分配本质上是个期望值问题,把时间花在把握大的板块上,总分才会更高。
6.2 手写代码时的细节问题
纸质笔试和在线OJ完全不同。没有编译器提示,没有自动补全,写错了也没有报错信息。这意味着你在纸上写代码时必须特别注重细节:变量名前后统一,括号配对齐,分号别漏,数组下标从0开始还是从1开始要写清楚。
还有一点是代码排版。纸质卷子上代码区域就那么大,如果你写得密密麻麻,阅卷人看半天找不到关键逻辑,印象分会受影响。我的做法是先在草稿纸上写一遍逻辑,确认没有低级语法错误,再工整地誊写到答题纸上。虽然多花几分钟,但换来的是清晰的结构和更少的涂改痕迹。
6.3 面试官后续追问的方向
笔试通过后还有面试环节,面试官会拿着你的笔试卷子追问。比如链表反转那题,你写了迭代法,面试官会问递归怎么写,两种方式的空间复杂度分别是多少;你写了O(n^2)的最长回文,面试官会问能不能优化到O(n),Manacher算法的核心思想是什么。
这就要求你不仅会写一种解法,还要理解解法背后的原理和不同方法之间的优劣。所以备考的时候,每道编程题都尽量掌握两种以上的解法,并且能讲清楚它们的复杂度差异和适用场景。这在恒生这类公司的面试里特别重要,因为面试官大多是做核心系统的老工程师,他们对底层细节的关注度远超普通互联网公司。
7. 针对恒生这类金融IT公司的备考建议
7.1 基础知识的优先级排序
如果你现在还在准备阶段,我给一个明确的优先级排序:C语言语法和内存模型首先看,其次是数据结构,然后是操作系统,再往后是计算机网络和数据库。这个排序不是拍脑袋定的,而是基于金融IT系统的实际技术构成。
C语言这块,重点复习指针、数组、字符串、结构体、位运算、内存管理(malloc/free配对使用、内存泄漏场景)、static/const/volatile关键字的语义。推荐把《C程序设计语言》里的经典例题全部写一遍,尤其是涉及指针运算和数组下标的题目,一定要在纸上画出内存图。数据结构方面,链表、栈、队列、二叉树、哈希表、排序算法是重点,图论可以放一放。操作系统重点关注进程线程模型、同步互斥机制、死锁条件、内存分配策略。网络方面,TCP/UDP协议细节、三次握手四次挥手、socket编程模型是高频考点。
7.2 项目经验的准备方向
笔试刷人靠卷面,面试定胜负靠项目。恒生的面试官对项目的考察方式跟互联网公司不太一样,他们更关心你的代码在真实环境下能不能稳定运行。比如你做过一个Web项目,他们会问:数据库连接池怎么配置的?并发量上来的时候会不会连接泄漏?你有没有做过压测?这些问题的核心逻辑是“你有没有工程意识”。
我建议在简历里放一个能体现“对稳定性和性能有思考”的项目,哪怕是一个课程设计,只要你在里面考虑过内存管理、并发控制、容错处理,就能讲出亮点。完全没有真实金融项目经验也没关系,面试官更看重的是你有没有这种思维方式。在面试前,把项目里用到的每个技术点都准备好Why和How,不要停留在“用了什么技术”,要能讲出“为什么用这个而不用别的,踩过什么坑,怎么解决的”。
7.3 刷题资料与笔试环境模拟
刷题资料方面,不推荐直接上LeetCode困难题。先把《数据结构》(C语言版)里的经典题目过一遍,然后用LeetCode的简单和中等题练手感。重点做这几类:数组、字符串、链表、二叉树、排序、二分查找、DFS/BFS、简单的DP。动态规划不需要刷太多,能掌握基本的背包、最长公共子序列、最长回文子串就够用了。
笔试环境模拟也很重要。恒生这种纸笔笔试,跟在线笔试是完全不同的体验。你可以自己模拟:找一套真题或者模拟题,打印出来,计时90分钟,用笔在纸上答题。这个过程能让你提前适应“没有编译环境、全靠手写”的节奏,也能暴露你在语法细节上的薄弱点。我当年就是这样模拟了三次,真正考试的时候心里就稳多了。
写在最后:这套题在今天的参考价值
从2015年到现在,恒生笔试的具体题目一直在变,但考察的内核没有变过——基础扎实、代码严谨、思维清晰。这套2015年的笔试题之所以还值得拿出来分析,是因为它代表了一类公司的招聘理念:不追逐热点技术词汇,不靠偏题怪题难为人,而是老老实实地考你在大学课堂上学过、但未必真正掌握的东西。
我个人后来的实际感受是,当年在笔试里被考到的那些概念——内存对齐、指针和数组的区别、进程与线程的边界条件、TCP握手背后的原理——后来在工作中全部用上了。所以如果你正在准备类似的笔试,别急着刷难题,先把基础的书看厚再读薄。做题的时候多问自己一句“这个结论为什么成立”,比多刷一百道题更有用。