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

资讯详情

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

Google 2013笔试题复盘:算法思维、系统设计与工程实践的深度解析

Google 2013笔试题复盘:算法思维、系统设计与工程实践的深度解析 1. 为什么我到现在还把2013年这份卷子供起来2013年那会儿我还在准备各种招聘笔试当时在技术社区里偶然翻到一份被标注为“Google2013笔试卷”的题目集合。说实话第一次做的时候我没能完整做完有些题直接在草稿纸上卡了半小时。后来我把这份卷子当成一面镜子每次觉得编码水平不错了就拿出来重新刷一遍看自己能不能比上一次多推导出几步。先把话说清楚Google当年的笔试题并没有一个官方统一版本。很多流传在外的扫描件、回忆版、复现版内容并不完全一致我手里这份也不是原始纸质存档。所以这篇文章里出现的所有题目都是我基于当时流传的材料、以及公开讨论中公认的题型筛选出来的典型复现题。你可以把它们理解为“2013年 Google 风格算法题”而不是官方原卷的百分百照抄。如果你冲着原封不动的笔试卷来那我这里没有但如果你是冲着“这种风格的技术题究竟怎么练、怎么答、怎么避坑”来的这篇应该很适合你。为什么要回头研究2013年的卷子因为那几年的选题风格和后来LeetCode题库化之后的套路有明显区别。2013年的卷子很多题第一眼看会觉得很简单但它会在问题描述里一点一点加条件比如从“找到有序数组中的一个数”变成“找旋转数组的数”再到“两个有序数组的中位数”最后要求时间复杂度压到 O(log(mn))。整个过程像剥洋葱每剥一层都有新的约束逼着你去想更本质的解法。这种“简单题给出强约束”的命题方式非常有代表性。如果你正在准备算法面试或者对技术公司考察工程师的逻辑感兴趣甚至单纯想找一套有深度的思维训练题来挑战自己这篇文章都值得看。我建议你准备一支笔和一个本子先不要看我的推导过程把第三章里放出的三道题自己写一遍。写完再对照读收获会是直接看答案的两倍以上。2. 这套卷子的六个考察方向背后是一整套选人逻辑如果只盯着题目本身很容易陷入“背解法”的误区。但把整套卷子拆开看你会发现命题人的意图非常集中不是考你会不会某个冷门算法而是考你在模糊条件和时间压力下能不能保持清晰的工程技术判断。我把反复出现的题型归纳成了六个方向这六个方向也基本对应了技术岗对工程师的核心预期。2.1 算法与数据结构重点不在“会写”而在“会选”数组、字符串、二叉树、图、哈希表、堆这些数据结构在整套卷子里都会被覆盖。但真正的考察重点不是“你是否听说过红黑树”而是“你面对同一个问题时能不能在两个方案之间做出合理的取舍”。举个例子“找到数组中第k大的元素”。最简单的做法是排序时间复杂度 O(n log n)用快速排序的 partition 思想能做到期望 O(n)用一个大小为 k 的最小堆能做到 O(n log k)。笔试卷往往不会只要求你写出其中一个解法而是会追问数据量变成一亿条怎么办内存只有几十MB怎么办要求在线实时查询怎么办这一连串追问下来你才意识到题目想看的不是那个“正确答案”而是你在约束变化下的决策过程。我自己复盘时发现容易得分的方式其实是“先给出最直观的暴力解再主动提出优化”。这个顺序很关键。因为阅卷人可以看到你的思考起点也能看到你的优化动机。一上来就甩一个最优解反而会显得像是背过原题。2.2 数学与概率跨界的加分项Google 风格的笔试卷里一直保留着一部分数学和概率题这和它的产品基因有很大关系。那些需要处理海量数据、做在线推荐、做流量预估的工程场景对概率直觉的要求非常高。比如“生成一个圆内均匀分布的随机点”这道题表面上是一个概率题实际要靠坐标变换和概率密度函数的推导才能避免生成的随机点“中心密、边缘疏”。它考察的是你能不能把一个连续分布转成可以用均匀随机数生成的过程同时也考察你能不能通过数学工具验证自己方案的正确性。这种题不是背一个公式就能混过去的因为追问里会让你解释“为什么不是 r 直接取均匀”。2.3 工程实践代码不只是“能通过”很多人做笔试卷的时候习惯性地把代码当作“过题工具”变量名用 a、b、c函数里没有任何防御性判断。在2013年的卷子上这种做法会吃暗亏。评分看的不只是最终结果还有你的代码风格是否清晰、是否具备可维护性。我当时交卷后回头检查发现自己在边界条件上缺了很多判断比如数组为空时怎么办、越界时会不会崩、输入不是预期格式时会不会抛异常。后来我才意识到笔试环境里考官看的不仅是算法更是一个工程师有没有把代码写成“给下一个人读”的东西。这和我们今天熟悉的 Google C 代码风格规范里强调的“名字要自解释、格式要保持一致”其实是同一个价值观。2.4 系统与产品思维从单点到整体卷子里会有一类开放题比如“设计一个统计在线用户数量的功能”。它只给一句话的背景剩下全凭你自己补充条件。答这种题的关键是向阅卷人展示你的思考框架先划清系统边界再定义数据接口然后考虑数据规模、读写比例、实时性要求最后才是技术选型。这种题没有标准答案但如果你能主动说出“假设每秒有100万次请求”“读多写少”“允许秒级延迟”这样的假设你就是在展示一个工程师把模糊问题变成结构化方案的能力。反过来如果你只是空泛地说“用 Redis 存一个计数器”那基本上拿不到分数。因为缺掉了推理过程最终方案没有任何说服力。2.5 沟通与推导把你的思考过程写成可阅读的推导笔试虽然是纸面考核但你写下的推导过程本质上就是一次“无声的沟通”。比如算复杂度的题你不能只写最终复杂度还要把“为什么是这个复杂度”说清楚算数学期望的题你不能跳步跳步了就意味着逻辑断裂。2013年的卷子里有一个隐形的评分标准推导过程的完整度。同样一道题两个人答案都算对了但一个只写了最终数字一个写出了状态定义、转移方程、边界条件和复杂度推导后者的得分会明显更高。这一点在系统设计和概率题里尤其重要。2.6 时间压力下的取舍如果把整套卷子按一场考试来模拟时间其实并不宽裕。你不可能保证每道题都做到完美所以必须学会分配时间。我的策略是先看完整套卷子把题目按“熟”“不熟”“完全没头绪”分成三档。先做熟悉的那档确保基础分拿到再做不熟的控制每题不超过20分钟最后实在没思路的题就把能写的思路框架和部分代码写上去哪怕只写个暴力解和复杂度分析也能拿一些过程分。这六个方向的权重并不完全均分。算法与数据结构永远是最大头数学概率和系统设计次之工程实践和沟通推导则贯穿在每一道题里。清楚了这个比例复习的时候就不会一头扎进某个冷门算法里出不来了。考察方向常见题面风格面试官真正想看到的算法与数据结构设计算法、分析复杂度、回答追问约束变化时能否主动做方案取舍数学与概率推导过程、概率模型、随机过程能不能用公式和直觉验证方案正确性工程实践写完整可运行的代码边界处理、命名规范、可维护性系统与产品思维设计一个功能或系统能否把模糊问题拆解成可讨论的框架沟通与推导多步骤推理、解释每一步能不能把一个思路讲得让别人看懂时间压力整卷限时完成在时间有限时能否做出正确的战略取舍3. 三道让我反复修改的复现题与完整推导第三章是核心的实操内容。这三道题是我从整卷里挑出来、认为最具有代表性而且我当年做得比较吃力的题目。每道题我会先给出题面再带你走一遍从暴力解到优化解的推导过程最后附上代码和复杂度分析。3.1 题目一两个有序数组的中位数题面给定两个已经排序的数组nums1和nums2长度分别为m和n。请找出这两个数组所有元素的中位数要求时间复杂度达到 O(log(mn))。先说一个最直观的方案把两个数组合并、排序、取中间值。这样做时间复杂度和空间复杂度都是 O(mn)能够拿到答案但完全不符合题目的约束。笔试中的追问大概率会在这里出现所以我们必须给出二分方案。核心思路是把“找中位数”转换成“找一条分割线”。假设我们把nums1和nums2合并后视为一个整体那么中位数的含义是左半部分的元素都小于等于右半部分的元素并且左右两边的元素数量差不超过1。我们可以在较短的数组上做二分设分割点位置为i表示nums1左半部分包含i个元素同时设j表示nums2左半部分包含的元素数量两个数组左半部分合计为(mn1)/2个元素。为了保持平衡j可以算出来total_left (m n 1) // 2 j total_left - i然后我们检查分割线两侧的大小关系nums1[i-1] nums2[j] # 确保 nums1 左半部分的最大值不大于 nums2 右半部分的最小值 nums2[j-1] nums1[i] # 确保 nums2 左半部分的最大值不大于 nums1 右半部分的最小值如果第一个条件不满足说明i太大应该往左移动如果第二个条件不满足说明i太小应该往右移动。因为在短的数组上二分总复杂度为 O(log(min(m,n)))已经优于题目要求的 O(log(mn))。最终代码可以写成def find_median_sorted_arrays(nums1, nums2): if len(nums1) len(nums2): nums1, nums2 nums2, nums1 m, n len(nums1), len(nums2) left, right 0, m total_left (m n 1) // 2 while left right: i (left right) // 2 j total_left - i nums1_left_max nums1[i - 1] if i 0 else float(-inf) nums1_right_min nums1[i] if i m else float(inf) nums2_left_max nums2[j - 1] if j 0 else float(-inf) nums2_right_min nums2[j] if j n else float(inf) if nums1_left_max nums2_right_min and nums2_left_max nums1_right_min: if (m n) % 2 0: return (max(nums1_left_max, nums2_left_max) min(nums1_right_min, nums2_right_min)) / 2 else: return max(nums1_left_max, nums2_left_max) elif nums1_left_max nums2_right_min: right i - 1 else: left i 1这道题我在纸上推演的时候最容易错的地方有两个。一是i和j对应的左半部分元素数量容易搞混二是越界判断没有处理。写代码时建议先把-inf和inf的边界显式列出来再进入主逻辑。养成这个习惯之后后面写很多二分题都会顺手。3.2 题目二带障碍网格的唯一路径数题面给定一个m x n的网格起点在左上角终点在右下角每一步只能向右或向下走。网格中某些位置有障碍物不能通行。请问从起点到终点总共有多少条不同路径这道题在2013年的卷子里属于“基础DP题”但它有一个值得写进答案的优化点空间压缩。如果直接开一个二维数组状态转移方程很简单dp[i][j] dp[i-1][j] dp[i][j-1]如果(i,j)是障碍物则dp[i][j] 0这样的二维 DP 时间和空间都是 O(m×n)。能写出来基础分已经拿到了。但如果你想拿到更高的评价就要展示一个细节遍历每一行时当前行的状态只依赖上一行和当前行左边的状态因此可以用一个长度为n的滚动数组把空间复杂度降到 O(n)。伪代码如下def unique_paths_with_obstacles(grid): m, n len(grid), len(grid[0]) dp [0] * n dp[0] 1 if grid[0][0] 0 else 0 for i in range(m): for j in range(n): if grid[i][j] 1: dp[j] 0 elif j 0: dp[j] dp[j - 1] return dp[-1]这里有一个容易忽略的边界条件如果起点本身就是障碍物dp[0]必须直接置为0否则答案会错误。我当年第一次写这道题时起点的状态没处理好导致整个答案差了一个常数排查了很久才发现问题。笔试的时候这部分边界条件其实非常影响最终分数因为它体现的是“写代码时有没有考虑初始状态的正确性”。再往下想一层如果把“只能向右或向下”改成“可以向四个方向走”那这道题就不再是DP模型而变成了图上求路径数量或使用 DFS/回溯的问题。如果题目追加这个条件你要能立刻反应过来并说明现在为什么不能直接套用原来的状态转移方程。这就是2013年卷子常见的追问节奏。3.3 题目三圆内均匀随机点题面实现一个函数在单位圆内随机生成一个点要求点在圆面积上服从均匀分布。这道题看起来简单但真正动手写时很多人会犯同一个典型错误让角度θ在[0, 2π)上均匀分布同时让半径r在[0, 1)上均匀分布。这样得到的点的分布是中心密集、边缘稀疏并不均匀。原因在面积元上。圆的面积元是dA r * dr * dθ也就是说同样一段dr越远离圆心对应的实际面积越大。如果r直接取均匀随机数每个半径区间内生成的点的数量基本相同但这些点要被摊到越来越大的圆环面积上结果必然是靠近圆心的地方更密。正确做法是让半径的累积分布等于面积的累积比例。到半径为r为止的圆面积占整个单位圆面积的比例是r^2所以令随机变量R的分布函数满足F(R r) r^2如果U是[0,1)上的均匀随机数那么令r sqrt(U) θ 2π * V其中V也是[0,1)上的均匀随机数。这样生成的点就能在面积上均匀分布。代码如下import random import math def random_point_in_circle(): u random.random() v random.random() r math.sqrt(u) theta 2 * math.pi * v x r * math.cos(theta) y r * math.sin(theta) return x, y这类题给我们的经验是数学直觉不能只停留在“看起来对”的层面要用公式验证。如果你面试或笔试时遇到概率题适当写出“面积元是 r dr dθ所以半径分布不能均匀”这层推导考官就会放心得多。它证明你不是在背答案而是真的理解了随机过程的物理本质。4. 判卷视角下的踩坑记录代码整洁度和推导过程最容易被低估这篇文章写到这里我想认真聊一聊踩坑。你在笔记本上刷题的时候用IDE的自动格式化、自动补全、单元测试很多问题都会在不知不觉中被工具掩盖。但笔试卷是写在纸上的IDE给你兜底的能力瞬间归零。我把我体验过的几个坑列出来每一个都是真金白银换来的教训。第一个坑是变量名乱起。当年我在纸上写代码为了赶时间变量名全用a、b、c、tmp。写完自己回去检查发现逻辑已经混乱到必须逐行推演才能看懂更别说让阅卷人欣赏我的思路。后来我强制自己在写任何代码前把变量名想好再动笔比如left_max、right_min、total_left。这些名字本身就是注释阅卷人一眼就能看出你的思路脉络。第二个坑是“只写代码不写解释”。笔试卷很少要求你写长文注释但你可以在关键步骤旁边留一句简短说明比如“用二分维持分割线的平衡条件”。我当时觉得代码已经写得很清楚不用加这些但若干年后我自己回看那些卷子发现很多关键决策点已经想不起来当初为什么这样写。阅卷人面对成百上千份卷子不可能逐行揣摩你的意图。给关键步骤配一句说明是节省对方时间也是在给自己加分。第三个坑是边界条件偷懒。很多题在普通数据下没问题但数组长度为0、目标值不存在、输入全是障碍物、n1这类极端情况会直接暴露代码的脆弱。我现在的习惯是写完核心算法后立刻列几个边界样本口算一遍。比如二分数组时先跑m0或n0的情况做DP网格时跑1x1、1xn、带障碍物起点的情况。这个习惯在笔试卷上救了我好几次。第四个坑是依赖IDE。笔试前几个月我刷题都用IDE自动缩进、自动括号、语法高亮都开满。真正上纸笔环境时我才发现自己对缩进层次、括号匹配的感知非常迟钝甚至会出现“对着代码数不清楚花括号”的尴尬。后来我训练自己完全用纯文本编辑器、甚至直接用纸笔写代码把每一行往左往右移位都自己控制练熟了之后再回到IDE写代码反而会觉得思路更清晰。综合来看2013年这套卷子非常看重工程习惯。并不是说题目本身考你“代码规范”这个知识点而是说它在所有题目里潜伏着。比如 Google 的 C 风格风格强调“名字自解释、保持一致性”如果你平时就按这个习惯写码答题时会非常自然如果你平时靠 IDE 补全撑着纸笔环境下就会露馅。5. 我当时用一份2013年卷子做的复习路线图面对这种风格的卷子光靠刷题软件盲目刷是没有用的。我整理了一份比较实用的复习路线图按阶段推进每个阶段都对应具体的任务和检验标准。5.1 阶段一模拟考试环境限时破卷第一阶段不做别的就是限时模拟。找完整的三小时空闲时间关掉手机只留纸笔和一份卷子严格按一场考试的时间来。期间不允许查资料也不允许用IDE的调试功能。这一遍的目的不是拿满分而是摸清整卷的题量、难度分布和自己最耗时的题型。我当时第一次模拟卡在了一道概率题上超过半小时导致后面的系统设计题时间严重不足。这个体验比任何复习建议都直接它逼着我认识到自己的时间分配策略有问题。模拟结束后我会在卷子首页记下各种题型的耗时后面复习就知道该练什么。5.2 阶段二逐题复盘写解法笔记模拟完不是对答案就结束了。第二阶段我会把每道题单独拉出来整理一份“解法笔记”。笔记模板包括六项题号、题意关键词、核心算法、复杂度、边界情况、我的易错点。这一步非常枯燥但没有捷径。比如“中位数”那题我的笔记里写的是题意关键词是有序数组、中位数、O(log(mn))核心算法是二分分割线复杂度是 O(log(min(m,n)))边界情况是一个数组为空、两个数组长度奇偶不同易错点是分割线越过数组两端时如何处理。这样做完一遍之后我复习时只需要看笔记不需要再从题集中翻找原题。它能帮我快速建立起“题型到算法”的映射关系比盲目刷几十道类似题要高效得多。5.3 阶段三专项突破按类别刷题当你知道自己薄弱环节之后就可以脱离整套卷子做专题训练。我的薄弱点当年集中在二分查找和概率题所以花了大约两周时间每天只做这两个类别每类至少三题每道题都要求自己口头讲出推导过程。专项训练时可以刻意练一个方法拿到题目后先不写代码先用口述的方式把思路讲完。如果你的朋友在场就讲给朋友听不在场就录音讲给自己听。我试下来最好用的做法是把说明文字写下来。描述的过程中逻辑断点会自然暴露那通常就是你理解最薄弱的位置。5.4 阶段四纸笔编码训练手感和边界感最后一个阶段也是最容易被忽略的阶段用纸笔写代码写完后放到编辑器里跑。你可能会发现自己用手写的代码很容易出现括号不对、缩进不齐、循环变量越界这类低级错误。我当时的做法是把每个专题挑一到两题用纸笔完整写一遍不经过IDE自动格式化然后拍照或者手动录入电脑运行。第一遍通常跑不过坚持改几遍之后手上的“代码肌肉记忆”会越来越准。要特别留意的测试数据包括空数组、单元素数组、全障碍物网格、极大数字导致溢出的场景等。这几个阶段下来大概需要一个多月每天保持两小时左右。整套卷子看起来只有几道题但它带来的思维训练量并不亚于刷完整整一百道LeetCode。关键区别在于这套卷子是综合性的它逼着你在算法、数学、系统设计、工程习惯之间来回切换而不是在同一个题型里机械重复。6. 最后的一点心里话笔试卷不是终点它是工程师的思维体操做完一份2013年的笔试卷最大的收获并不是“我记住了多少题”而是它逼着我重新审视了“什么叫作把一个问题讲清楚”。无论是二分法的边界条件还是概率题的面积元推导本质上都在训练一件事任何一个结论都要能够沿着逻辑链条从头走到尾中间不能有含糊的跳跃。我后来在平时工程里做的不少事情其实都和这套卷子有着隐秘的联系。写代码时多想的那个“如果输入是空怎么办”调接口时多算的那笔复杂度解释方案时多写的那行注释源头都能追溯到当年在纸条上反复摩擦的那些算法题。它不像一门具体的业务技术那么“实用”但它会持续影响你看待问题的方式。如果你准备把这份卷子认真刷一遍我的建议是找个安静的晚上把题目抄在纸上不看任何参考提示先独立推演一个小时。能推多少算多少推不出来也没关系那种“卡住之后重新再试”的过程本身就是最值钱的训练。过了这一关之后你再回来看别人的解题思路会有完全不同的体会。
返回列表