开头先交代一个背景:我是把这份A卷当作一个“体检项目”来做复盘的,不是简单地背答案,而是想把每道题背后的考察点翻出来。当时拿到这套帆软2019届春招研发岗位A卷,第一感觉是卷面风格很务实,不故意挖坑,但基础知识不扎实的话,很容易在看似普通的题上栽跟头。
帆软做的是报表和商业智能工具,研发岗位笔试天然就带“数据基因”。这意味着除了常规的语言、数据结构和算法,数据库和场景设计题占了相当大的比重。如果你准备的是互联网大厂那种偏重算法竞赛的题库,没必要照搬到这里;这份卷子更看重“能不能用工程化思维解决数据展示与分析问题”。
这篇复盘面向两类人:一类是正在准备校招、尤其是瞄准国内软件或BI厂商研发岗的同学;另一类是工作几年想跳槽、但很久没碰笔试的人。我会按卷面结构逐块拆解,每道有代表性的题都会讲清楚“考点是什么、我当时怎么想、标准做法长什么样、还有哪些坑”。
1. 这份A卷在考什么:整体卷面结构与考察意图
1.1 卷面模块与常见分值分布
从试卷结构来看,A卷基本沿用了“基础选择题/填空题 + 算法编程题 + SQL题 + 场景设计题”的组合,整体时长一般在90到120分钟。具体模块大致可以分成这样几块:
| 模块 | 常见题型 | 大概分值比重 | 核心考察方向 |
|---|---|---|---|
| 语言与数据结构基础 | 单选、多选、判断、简答 | 25% - 35% | Java/C++语法、集合框架、内存模型、链表栈队列 |
| 算法与编程 | 在线编程题 | 25% - 35% | 字符串处理、数组遍历、动态规划、贪心 |
| 数据库 | SQL编写、查询优化问答 | 20% - 30% | 多表关联、分组聚合、子查询、索引 |
| 综合设计 | 开放问答、场景设计 | 15% - 20% | 报表取数、权限控制、性能优化 |
这个配比很能说明问题:帆软不是靠刷题海选人的公司,它希望你具备完整的逻辑链条——看懂数据结构、会写程序、能查数据库、最后还能把业务抽象成可落地的方案。我后来和几个同学交流,大家普遍觉得算法题难度中等偏上,但SQL和设计题才是真正拉开差距的地方。
1.2 “帆软系”出题风格的三个共性
这里说的“帆软系”,泛指做报表、BI、数据可视化这一类ToB软件的厂商。他们的笔试和互联网C端产品有个明显区别:题目背景很落地,往往直接来自真实的客户需求。总结下来有三个共性:
第一,不追求偏题怪题。你很少看到那种需要冷门数学技巧或脑筋急转弯式的算法题,更多是“给定一个字符串,按规则输出”这种平铺直叙的题目。但平铺直叙不等于送分,边界条件处理不好照样过不了全部测试用例。
第二,数据库相关题目占比高。要知道报表工具的核心工作之一就是从各种数据库里取数、聚合、格式化输出,所以SQL能力几乎是研发岗的生存技能。笔试里的SQL题通常不会只考单表查询,多表关联、分组统计、行列转换都是高频考点。
第三,场景设计题占一席之地,而且基本绕不开权限、性能、大数据量这三个话题。帆软的FineReport和FineBI都涉及多用户协同、多数据源接入,所以“怎么让成千上万人同时打开一张报表还不卡”这类问题,几乎每年都会出现。
2. 基础题里的那些“送命题”:语言与数据结构易错点
2.1 语言基础:Java内存、字符串与finally的经典组合
基础题不是白送分的,它筛掉的是“基础不牢靠”的人。我记得A卷里有一道典型的Java题:给出一个方法,try块里return一个int,finally块里修改这个int,问最终返回值是多少。初看似乎简单,实际上考察的是对Java返回值和finally执行机制的理解。
正确结论是:try块中的return先计算并保存返回值,然后才执行finally块,finally块对局部变量的修改不会影响已保存的返回值。但如果finally块里也有return,则会覆盖try里的返回值,这个变体也经常考。
类似的还有String不可变性、StringBuilder和StringBuffer的区别、ArrayList与LinkedList的适用场景。这些题目本身不超纲,但恰恰是平时写代码时最容易忽略的地方。我的建议是不要只背结论,要去看JVM字节码或直接写代码验证。自己跑一遍,比看十篇面经都管用。
2.2 数据结构与C/C++考点:内存对齐与链表操作
A卷中还有一部分C语言或C++相关的题目,即使是投Java岗,也偶尔会碰到几道。这里最经典的就是内存对齐计算。比如:
struct Test { char a; int b; char c; };问sizeof(struct Test)是多少?如果你按字段大小相加,会得到1+4+1=6,但正确答案是12(在32位和64位默认对齐规则下)。原因是编译器会在char a之后填充3个字节,让int b对齐到4字节边界;结构体整体大小再对齐到最大成员对齐数的整数倍,即4的倍数,所以char c之后还要再填充3个字节。这个考点考察的是对计算机内存布局的理解,而不是单纯的语法记忆。
链表相关的题也很常见,比如判断单链表是否有环、找环的入口、反转链表。这类题在笔试里通常以代码补全或简答形式出现,算法本身不复杂,但手写时容易在指针边界上出错。我的经验是:链表题一定要画图,画出每一步指针指向,再动笔写代码,这样基本不会写乱。
2.3 基础题复习建议:别只看不练
基础题想拿高分,没有捷径,就是反复练。但“练”不是盲目刷题,而是针对自己的薄弱点定向突破。如果你对JVM内存模型搞不清楚,就把运行时数据区、GC流程、类加载机制串一遍;如果对C语言指针发怵,就专门做指针和数组的对比练习。
建议自制一份错题清单,把每次笔试或模拟中做错的题归类,标出考点和错因。比如“String不可变性”是一个考点,“finally执行顺序”是另一个考点。等到考前只看清单,效率会比重新翻书高很多。
3. 算法编程题:从题意到AC的完整推演
3.1 高频题型:字符串处理、模拟实现和贪心
A卷的编程题一般有两到三道,核心是考察“把思路转成代码”的能力。我在多个版本的题目里都见到过这些类型:字符串去重、字符串排序、数组最大连续子段和、根据规则模拟某个过程。
不要小看字符串处理,这类题最容易出现“逻辑没想清楚就动手写”的问题。比如要求统计字符串中每个字符出现的次数,并按次数从大到小输出,次数相同的按字符ASCII码升序输出。很多人在排序规则上栽跟头——写成了先按字符排、再按次数排,和题目要求的优先级正好反了。
给一个可复现的解法,用Python写会非常直观:
from collections import Counter def sort_by_count(s: str) -> str: counter = Counter(s) sorted_chars = sorted(counter.items(), key=lambda x: (-x[1], x[0])) return ''.join([c * cnt for c, cnt in sorted_chars])这段代码里,关键是sorted的key函数同时指定了两个维度:次数降序、字符升序。Python的tuple比较会先比较第一个维度,再比较第二个维度,所以(-x[1], x[0])就能一次搞定。如果笔试环境允许用Python,这样写最省时间;如果只允许C++或Java,思路一样,只是需要自己实现排序比较器。
3.2 贪心与动态规划的典型例题推演
另一道我在A卷里印象很深的题是“跳跃游戏”变种:给定一个非负整数数组,初始位置在第一个下标,每个元素代表你在该位置可以跳跃的最大长度,判断能否到达最后一个下标。如果只是判断能否到达,用贪心就够了:
维护一个最远可达位置max_reach 遍历每个位置i: 如果i > max_reach,说明到不了当前位置,返回false 更新max_reach = max(max_reach, i + nums[i]) 如果最后max_reach >= 数组长度-1,返回true这种题考察的核心不是算法本身有多难,而是你是否能想到用“动态更新最远可达距离”替代DFS或BFS。很多同学一开始会想用递归,最后超时了才意识到该用贪心。笔试时间有限,遇到这种“找最值、判断可达性”的题,第一时间就该往贪心或动态规划上靠。
写代码时还要注意输入输出格式。在线笔试平台(牛客、赛码之类)通常要求自己处理输入,不同题目可能是单行输入也可能是多行测试用例。我的习惯是先写一个处理单组输入的版本,再考虑是否要用while循环读入多组。很多同学不是不会算法,而是卡在readline的处理上,白白丢了分。
3.3 手写代码与IDE环境的差异:提前适应
笔试环境一般没有自动补全,甚至有的平台连本地编译都不让,这就很考验“裸写代码”的功底。我在准备阶段做了个训练:不用IDE,直接用纯文本编辑器或者在线笔试平台的模拟环境写代码,写完再复制到本地编译跑测试用例。这个过程一定要做,不然上考场会发现连string头文件都忘记include。
另外,笔试时看清楚平台对“函数式提交”和“ACM式提交”的要求。函数式提交只需要你补全核心函数,ACM式则要求你自己定义输入输出。两者的编码方式差别很大,建议提前上牛客或LeetCode的“笔试模式”里练几道题。
4. SQL与数据库:报表公司笔试的隐藏重头戏
4.1 必考SQL类型:分组统计、多表关联与子查询
我之前说过,报表工具厂商的笔试里SQL是重头戏。A卷的SQL题通常会给出若干张表,要求你写出某个查询结果。高频考点包括:
- GROUP BY和HAVING组合使用
- 多表JOIN(内连接、左连接)和子查询的取舍
- WHERE和HAVING的执行顺序
- 聚合函数(SUM、COUNT、MAX、MIN)配合CASE WHEN做条件统计
这些考点如果只看语法书会觉得简单,但一旦结合具体表结构,很多人就会忽略“去重”“NULL值”这些细节。比如统计每个部门的员工数,如果员工表的dept_id存在NULL,COUNT(*)和COUNT(dept_id)的结果就会不一样,这在实际报表中非常关键。
4.2 典型题目:查询每个部门工资最高的员工
A卷里出现过一道非常经典的表结构题,大意是员工表(employee)包含员工ID、姓名、部门ID、工资,部门表(department)包含部门ID、部门名称,要求查询每个部门中工资最高的员工姓名和工资。
第一种写法是用相关子查询:
SELECT d.dept_name, e.emp_name, e.salary FROM employee e JOIN department d ON e.dept_id = d.dept_id WHERE e.salary = ( SELECT MAX(salary) FROM employee e2 WHERE e2.dept_id = e.dept_id );第二种写法是用窗口函数,逻辑更清晰,性能也往往更好:
SELECT dept_name, emp_name, salary FROM ( SELECT d.dept_name, e.emp_name, e.salary, ROW_NUMBER() OVER(PARTITION BY e.dept_id ORDER BY e.salary DESC) AS rn FROM employee e JOIN department d ON e.dept_id = d.dept_id ) t WHERE rn = 1;这里有两个要点:一是如果同一个部门里有多个员工工资并列第一,用ROW_NUMBER()会只保留一个,用RANK()或DENSE_RANK()才能保留多个,笔试时要看清题目问的是“一个”还是“所有”;二是很多在线笔试平台默认的MySQL版本较低,可能不支持窗口函数,这时候就写相关子查询版本更稳妥。
4.3 报表场景SQL:行列转换与同比环比
帆软的业务核心是报表,所以笔试里还有一类贴近业务的SQL题:给定订单表(包含订单日期、地区、销售金额),要求按月统计销售总额,并输出每个月的同比或环比数据。
这种题的关键是日期函数的使用。比如按“年-月”格式化字段:
SELECT DATE_FORMAT(order_date, '%Y-%m') AS month, SUM(amount) AS total_amount FROM orders GROUP BY DATE_FORMAT(order_date, '%Y-%m') ORDER BY month;如果要算环比,可以和上一期的数据做自关联,或者用LAG窗口函数。在MySQL 8.0中,LAG(SUM(amount)) OVER (ORDER BY month)就能拿到上个月的值。这类题不仅考SQL语法,更考“能不能理解业务指标的计算逻辑”,和报表研发的日常工作非常贴近。
4.4 SQL优化:索引失效与慢查询排查
除了写SQL,A卷里还有简答题,比如“某条查询很慢,你会怎么排查”。回答这类问题要分几步走:
- 先用EXPLAIN查看执行计划,确认是否走了索引,还是全表扫描。
- 检查WHERE条件里的字段是否有索引,函数操作、隐式类型转换、前导通配符都是导致索引失效的常见原因。
- 看看是否返回了过多字段,有时候SELECT *会把不需要的大字段也捞出来。
- 数据量级大时,考虑分页、缓存或汇总表。
我的经验是,回答优化问题时不要只背“索引失效”那几条,最好能结合场景说明排查过程。比如“先看慢查询日志定位SQL,再用EXPLAIN看扫描行数,然后看有没有没必要的大字段返回”,这样会让面试官觉得你真的处理过线上问题。
5. 场景设计题:报表工具研发眼中的“业务题”
5.1 高频场景:权限控制、大报表加载慢、多部门共用模板
场景设计题是A卷里最“活”的部分,通常没有标准答案,但回答得有没有逻辑,一眼就能看出来。我梳理几个高频方向。
权限控制:题目常描述成“一个企业有多个部门,不同部门的人只能看自己部门的数据,部门经理能看整个部门的数据,公司领导能看到所有数据,怎么设计权限体系”。这其实是典型的行级权限问题。可以设计“用户表 + 角色表 + 数据权限规则表”:用户关联角色,角色关联数据规则,规则里用SQL片段或部门ID集合限制可见行。报表在取数时动态拼接权限条件,底层是“权限过滤下推”,而不是查完再过滤。
大报表加载慢:有一类问题是“一张报表关联了5张表,数据量千万级,每次打开要几十秒,怎么优化”。我会从四个层面回答:数据源层面,看能不能在数据库里先做好聚合,或者建中间汇总表;报表层面,开启分批取数、按需加载,首屏只渲染前100行;缓存层面,对很少变化的数据做定时缓存;架构层面,如果并发高,考虑读写分离和查询集群。这种“分层优化”的回答方式最能体现工程思维。
多部门共用模板:这种情况在帆软这种报表工具的使用场景中非常常见。同一个模板,不同部门填不同的数据,最终汇总。这就要考虑模板参数化设计、填报权限和提交校验逻辑。笔试题目往往会简化成“你怎么设计一张支持多部门填报的报表”,回答时需要先明确填报流程,再设计表和报表参数,最后考虑并发提交时的数据一致性。
5.2 开放题应答框架:需求理解、约束识别、方案落地、风险评估
很多同学看到设计题就发怵,觉得无从下手。其实可以用一个相对固定的框架来组织回答:
- 需求理解:用自己的话复述一遍题目,确认要解决的核心问题是什么。
- 约束识别:指出数据量、并发量、实时性要求、可维护性等约束条件。
- 方案设计:给出分层、分模块的解决方案,尽量具体到表结构、接口或机制。
- 风险评估:主动说出方案的不足,以及后续如何演进。
比如回答权限设计题时,说完用户-角色-数据规则后,可以补一句“这种方案在规则数量特别多时,拼接SQL会产生较长的WHERE条件,需要测试对查询性能的影响;后续可以考虑把规则缓存到Redis”。这样做并不是画蛇添足,而是展示你有全局观和风险意识。
6. 时间分配与备考策略:来自过来人的实操建议
6.1 笔试现场的时间分配参考
A卷题的总体量不算大,但不代表写得完。我建议按“先易后难、分值优先”的策略来分配时间:
- 拿到卷子先花1到2分钟扫一遍全部题目,标记出明显会做的、需要思考的、基本没思路的。
- 先把基础选择题和填空题快速做完,遇到犹豫的题做个标记,不要死磕,控制在20分钟以内。
- 算法编程题每道给15到20分钟。如果10分钟还没思路,先写下暴力解法的代码,至少能通过一部分用例,然后再想优化。
- SQL题看起来不难,但手写容易漏条件,建议每题留10分钟左右,写完检查一遍GROUP BY和JOIN条件。
- 场景设计题放最后,用前面说的应答框架组织语言,写清楚要点即可,不需要长篇大论。
这个时间分配不是死规则,但核心思想是:不要在单题上恋战,笔试是通过性考试,多拿一分是一分。
6.2 知识盲点自查清单
结合这份A卷的特点,我整理了一份考前自查清单,你可以对照着打勾:
- [ ] Java/C++的语法基础:字符串、集合、异常处理、内存模型
- [ ] 数据结构:数组、链表、栈、队列、二叉树、哈希表
- [ ] 高频算法:排序、二分查找、快慢指针、滑动窗口、贪心、简单DP
- [ ] SQL语法:JOIN、GROUP BY、HAVING、子查询、窗口函数
- [ ] SQL优化:索引失效场景、EXPLAIN执行计划、慢查询排查
- [ ] 场景设计:权限设计、报表性能优化、多部门填报、缓存机制
- [ ] 笔试环境:输入输出处理、函数式提交与ACM式提交的切换
如果你能把这七项都过一遍,本身就是一个完整的知识梳理过程。没必要追求每个知识点都精通,但至少要做到“看到题知道在考什么”。
6.3 笔试之外同样重要的细节:简历与投递时机
笔试只是校招的一环,最终能不能拿到Offer,还取决于简历筛选、面试表现和沟通能力。就春招而言,时间窗口比较短,很多时候是补录,所以投递时机很关键。建议早投、多投,不要等所有题刷完再投,边投边刷反而效率更高。
简历上如果写了熟悉报表工具或参与了数据可视化项目,面试官大概率会在笔试后追问相关细节,所以简历里的每一句话都要能展开讲。尤其注意,帆软是国产报表龙头之一,如果你在简历里提过FineReport或FineBI,至少要把“用它们做过什么、遇到什么问题、怎么解决”说清楚。
在写代码和SQL时,我也建议养成写注释和格式化输出的习惯,笔试平台虽然只看结果,但面试官回看代码时,清晰的变量命名和简洁的逻辑结构会留下好印象。
最后说一点个人体会:笔试复盘比刷题更重要。每次做完一套题,把错题和犹豫题整理成考点清单,两周后再翻一遍,效果远好于漫无目的地刷下去。这份A卷复盘最大的价值,不在一道题的解法,而在于帮你摸清“研发岗笔试题到底在测什么”。弄明白了这一点,不管下次面对的是哪家的卷子,都能从容不少。