
前端算法面试题解大全这个标题乍一看像是又一份题单合集但我做了几年面试官、也帮不少人做过模拟面试之后发现真正让人卡住的往往不是题没见过而是没搞清楚前端岗位考算法的边界在哪里。有人把后端那套图论、并查集、线段树全刷了一遍结果面试现场被问的是手写一个带取消功能的防抖也有人觉得前端不考算法白板上连数组去重都写不干净。这篇内容我想做的事情是把前端算法面试这件事拆成可操作的几块考什么、怎么归类、手写题的评分点在哪、训练节奏怎么安排、现场怎么应对。适合正在准备面试的初中级前端也适合工作几年但算法这块一直是短板、想系统补一遍的人。下面所有代码都是 JavaScript 实现思路和取舍我会尽量讲透而不是只丢一段答案。1. 前端算法面试的考察边界力气该花在哪1.1 为什么前端岗也要考算法很多人对这个问题的理解停留在考察逻辑思维这种空话上。我的观察是前端岗位引入算法题主要解决三个现实问题。第一是筛选成本简历上写着精通 JavaScript的人太多一道中等难度的题能在二十分钟内把能写业务代码和能想清楚问题区分开。第二是岗位本身的需求前端现在大量涉及数据处理——表格几万行数据的排序过滤、可视化里的坐标计算、编辑器里的文本 diff、虚拟列表的区间计算这些东西写不出来就是写不出来跟框架熟不熟没关系。第三是沟通能力的载体算法题是一个天然的、有明确对错的小型协作场景面试官想看你在有压力的环境下怎么拆问题、怎么表达思路。理解这一点很重要因为它决定了你准备的方向。如果只是逻辑思维那刷什么题都行但如果是数据处理能力 沟通表达那重点就应该放在数组字符串操作、树形结构遍历、以及能讲清楚复杂度这几件事上。我见过候选人一上来就写了个三层嵌套循环然后说这个应该能过面试官当场就沉默了——不是因为答案错而是因为对复杂度的敏感度不够而这在实际业务里是会出事的。1.2 不同层级与业务线的权重差异这块差异比大多数人想象的大。我大致按经验和业务类型分了一下是我自己面过和旁观过的粗略感受不是绝对标准岗位情况算法题占比常见难度更看重的点初级/校招中等偏高简单到中等基础扎实、边界处理、代码整洁中级社招中等中等复杂度分析、优化过程高级/资深中等偏低中等偶有偏难思路表达、方案取舍、工程判断业务偏重中后台、营销页偏低简单手写工具函数、场景设计题偏底层编辑器、可视化、基建偏高中等偏难数据结构选型、性能敏感度从这个表能看出来一个反直觉的结论级别越高算法题本身反而没那么难但对你为什么这么选的追问会更狠。资深岗被问一道中等题面试官往往不会满足于 AC而是会接着问如果数据量涨到一千万条呢如果要求流式处理呢内存受限怎么办。这时候背模板是没用的得有真实的判断。反过来校招和初级岗的题不难但评分非常看细节。空数组、单元素、重复元素、负数、超长字符串这些边界漏一个就掉一档。我带过一个实习生思路完全正确但写的函数在输入为空时直接抛异常面试官的评价是工程习惯还需要打磨。这句话听起来客气实际上就是没过。1.3 明确不该花时间的地方说点省时间的。前端算法面试里下面这些内容出现的概率极低除非你面的是明确偏算法的岗位复杂的图论算法最小生成树、网络流、高级动态规划状态压缩、树形 DP 的复杂变形、线段树与树状数组、字符串匹配的 KMP 及更复杂的自动机、计算几何。这些不是没用而是投入产出比在这个场景下很差。我个人的排序是数组字符串的手写熟练度 树的递归思维 链表指针操作 栈队列的典型应用 基础 DP 其他。前三项吃透了八成的前端算法面试能稳住。剩下的时间我觉得花在手写业务工具函数上更划算比如深拷贝、防抖节流、事件总线、Promise 并发控制这些是前端面试真正的高频区而且答得好能直接体现工程能力。2. 题型压缩把几百道题收拢成六条主线题海是刷不完的但题型是有限的。我习惯把前端会遇到的算法题归成六类每类记住两三个母题和它的变形方向遇到新题就往里套。这个方法的好处是你不需要记住答案只需要认出这是哪一类。2.1 数组与字符串双指针和哈希表这两板斧这一类占了我见过的题里最大的比例核心就两个套路。哈希表解决找配对、去重、计数的问题用空间换时间把 O(n²) 压到 O(n)。双指针解决有序数组、区间、子串的问题包括对撞指针、快慢指针、滑动窗口三种形态。拿最经典的两数之和举例暴力解法是双重循环面试官一般不会让你停在这// 哈希表解法时间 O(n)空间 O(n) function twoSum(nums, target) { const map new Map(); for (let i 0; i nums.length; i) { const need target - nums[i]; if (map.has(need)) { return [map.get(need), i]; } map.set(nums[i], i); } return []; }这段代码有两个地方值得在面试里主动说出来。一是边遍历边建表而不是先建完整表再遍历前者能天然避免同一个元素用两次的问题后者需要额外判断索引是否相同。二是返回空数组而不是 null保持返回类型一致这是工程习惯的体现。这两个点说出来比代码本身更能加分。滑动窗口是这类题里最值得花时间的一块因为它的模板性最强。核心逻辑是维护一个窗口右边界扩张、左边界收缩在过程中更新答案。不管是最长无重复子串最小覆盖子串还是长度最小的子数组骨架都是一样的// 最长无重复字符子串滑动窗口模板 function lengthOfLongestSubstring(s) { const seen new Map(); let left 0; let maxLen 0; for (let right 0; right s.length; right) { const ch s[right]; // 出现过且位置在当前窗口内左边界跳过去 if (seen.has(ch) seen.get(ch) left) { left seen.get(ch) 1; } seen.set(ch, right); maxLen Math.max(maxLen, right - left 1); } return maxLen; }这里最容易错的是seen.get(ch) left这个判断。如果不加这个条件遇到 abba 这种就会算错因为第一个 a 的旧位置已经不在窗口里了。我建议把这个细节当成肌肉记忆记下来因为面试官很爱拿重复字符的边界来试你。2.2 链表指针操作的肌肉记忆链表题在前端面试里出现频率中等但一旦出现通常就是考察你对指针操作是否真的清楚。核心只有三件事怎么存下一个节点、怎么断链、怎么处理头节点。反转链表是绝对要背到能闭眼写的// 反转链表迭代写法 function reverseList(head) { let prev null; let cur head; while (cur) { const next cur.next; // 先存 cur.next prev; // 再断 prev cur; // 后移 cur next; } return prev; }写的时候顺序不能乱先存 next 再改 cur.next反过来链就断了。我在模拟面试里见过不少人顺序写反然后盯着屏幕找 bug 找了五分钟这种失误在真实面试里非常伤因为它暴露的是没真正理解。另外两个高频点是快慢指针找中点、以及合并两个有序链表。前者是回文链表、环形链表的基础后者是归并排序链表版本的基础。链表题的通用心法是加一个哑节点dummy head把头节点特殊处理这个麻烦事消掉// 合并两个有序链表用哑节点简化边界 function mergeTwoLists(l1, l2) { const dummy { next: null }; let tail dummy; while (l1 l2) { if (l1.val l2.val) { tail.next l1; l1 l1.next; } else { tail.next l2; l2 l2.next; } tail tail.next; } tail.next l1 || l2; return dummy.next; }哑节点这个技巧值得单独记一下删除节点、合并链表、分割链表都用得上能省掉大量 if 判断。面试时提一嘴这里用哑节点避免处理头节点为空的情况是很自然的加分表述。2.3 二叉树递归模板与遍历变形树相关的题本质上是递归思维的考察。前端里树结构到处都是DOM 树、组件树、菜单树、权限树、文件目录所以这块的实战意义很强不是纯应试。递归题的固定套路是先想清楚三件事递归函数的语义是什么输入什么、返回什么、终止条件是什么、单层递归要做什么。以最大深度为例// 二叉树最大深度 function maxDepth(root) { if (!root) return 0; return Math.max(maxDepth(root.left), maxDepth(root.right)) 1; }语义是返回以 root 为根的树的最大深度终止条件空节点返回 0单层逻辑是取左右子树较大值加一。只要这三件事清楚了绝大多数树的题都能推出来。层序遍历BFS是另一个必须掌握的模板因为很多按层的题目都靠它// 层序遍历每一层作为数组返回 function levelOrder(root) { if (!root) return []; const result []; const queue [root]; while (queue.length) { const levelSize queue.length; const level []; for (let i 0; i levelSize; i) { const node queue.shift(); level.push(node.val); if (node.left) queue.push(node.left); if (node.right) queue.push(node.right); } result.push(level); } return result; }这里的关键是levelSize这个快照它保证了你每次只处理当前层的节点。忘了这个就会变成一次性全展开。这个模式能直接套到每层最大值右视图之字形遍历上。2.4 栈与队列单调栈与滑动窗口最大值栈的经典应用是括号匹配和单调栈。后者在前端面试里出现得不算多但一旦出现往往和下一个更大元素柱状图中最大矩形绑在一起。单调栈的核心是维护一个单调递减或递增的栈遇到不满足单调性的元素就弹栈并结算// 下一个更大元素返回每个位置右侧第一个更大的值 function nextGreater(nums) { const res new Array(nums.length).fill(-1); const stack []; // 存索引对应值单调递减 for (let i 0; i nums.length; i) { while (stack.length nums[stack[stack.length - 1]] nums[i]) { const idx stack.pop(); res[idx] nums[i]; } stack.push(i); } return res; }注意栈里存的是索引而不是值这样才能写回结果数组。这个细节我在第一次学的时候也踩过存了值之后发现不知道往哪填。队列这块滑动窗口最大值用双端队列是标准解法。思路是维护一个索引队列保证队头永远是当前窗口最大值同时把过期的、以及比新元素小的都从队尾挤掉。这个题的代码不长但每一行都有道理面试里光写出来不够得能解释为什么队尾可以被放心弹出。2.5 动态规划从前端场景切入DP 是很多人的心理阴影。我的建议是先别啃那一堆背包问题从前端场景相关的入门题开始爬楼梯、最大子数组和、最长递增子序列、编辑距离这个和富文本 diff 有关系讲出来很有说服力。以最大子数组和为例状态定义是以 i 结尾的最大和转移方程是取接上前面的或从自己重新开始的较大值// 最大子数组和滚动变量优化空间 function maxSubArray(nums) { let prev nums[0]; let max nums[0]; for (let i 1; i nums.length; i) { prev Math.max(nums[i], prev nums[i]); max Math.max(max, prev); } return max; }这里用滚动变量把空间从 O(n) 压到 O(1)面试时主动提一句状态只依赖前一个所以不需要开数组会显得你对 DP 的空间优化有概念。DP 的学习心法我总结成一句话先写出暴力递归再找重叠子问题最后加记忆化或改成自底向上。跳过第一步直接背转移方程遇到变形必挂。因为转移方程的形态是跟着状态定义走的状态定义错了方程背得再熟也没用。2.6 场景设计题前端真正的主战场这一类严格说不算算法题但它是前端面试里最有辨识度的部分而且量大。典型题目包括防抖节流、深拷贝、带缓存的函数、Promise 并发控制、事件总线、数组扁平化与去重、日期格式化、模板字符串替换。防抖和节流我建议手写到能解释每一次边界。防抖是停止触发一段时间后才执行节流是固定频率执行// 防抖立即执行选项 取消能力 function debounce(fn, delay, immediate false) { let timer null; function debounced(...args) { if (timer) clearTimeout(timer); if (immediate !timer) { fn.apply(this, args); } timer setTimeout(() { timer null; if (!immediate) fn.apply(this, args); }, delay); } debounced.cancel function () { if (timer) clearTimeout(timer); timer null; }; return debounced; }这段代码能展开讲的点很多this的传递、immediate模式下第一次立即执行、cancel的用途组件卸载时清理。面试官如果只想要基础版你给基础版但如果他追问组件销毁了还在执行怎么办你能答出 cancel这就是区分度。深拷贝的坑更多循环引用、Symbol 键、Date/RegExp/Map/Set 的处理、函数怎么对待。我的建议是面试时先写一个能处理循环引用的基础版然后主动列出我暂时没处理的类型这比硬写一个看起来很全但全是 bug 的版本要好得多// 深拷贝基础版处理循环引用与常见类型 function deepClone(obj, cache new WeakMap()) { if (obj null || typeof obj ! object) return obj; if (cache.has(obj)) return cache.get(obj); let result; if (obj instanceof Date) return new Date(obj); if (obj instanceof RegExp) return new RegExp(obj); result Array.isArray(obj) ? [] : {}; cache.set(obj, result); Reflect.ownKeys(obj).forEach(key { result[key] deepClone(obj[key], cache); }); return result; }Reflect.ownKeys一次性拿到字符串键和 Symbol 键比Object.keys更全。WeakMap做缓存既解决循环引用又不阻止垃圾回收这两个点说出来就是加分项。3. 手写题的分水岭面试官在代码里找什么3.1 边界与异常处理同样的思路两个人写出来评分能差一档差距基本都在边界上。我列一下前端手写题里最常被漏掉的几类输入可以当检查清单用输入类型典型问题检查动作空值null / undefined / 空数组 / 空字符串函数第一行判空单元素长度为 1 的数组、单节点链表循环是否至少进一次重复元素去重、求和时的重复计数是否需要保持稳定极值超长数组、超大数字是否需要考虑溢出或性能非预期类型传了字符串当数字、传了对象是否做类型校验我见过一个很典型的例子写数组求和候选人写for (let i 1; i arr.length; i)从 1 开始直接把第一个元素漏了。这种错误在紧张状态下特别容易出现而练习时如果没有刻意检查形成习惯后很难改。我的建议是每写完一个函数花十秒钟默念一遍空、一、重、极、异这五个字。3.2 复杂度表达与优化路径写完代码不算完面试官几乎一定会问复杂度是多少。这里有个技巧不要只报答案把推导过程说出来。比如外层循环 n 次内层最多 n 次但内层指针不会回退所以整体是 O(n)这种表述比干巴巴一句O(n)有说服力得多。更关键的是优化路径。面试里常见的节奏是先给暴力解说明复杂度再说这里其实可以优化。哪怕你不确定能不能写出来说清楚优化方向也有分。比如两数之和暴力是 O(n²)因为对每个元素都要找一遍它的配对这个查找过程可以用哈希表变成 O(1)整体降到 O(n)。这段话一出来面试官基本就知道你懂。反过来如果直接给最优解但讲不清为什么最优评分往往不如暴力 清晰优化的组合。因为前者可能是背的后者一定是想的。3.3 代码风格与可读性这块说起来虚但实际影响不小。变量名a、b、temp、res1满屏飞和left、right、maxLen、windowStart这样的命名给人的观感完全不同。函数拆不拆、注释写不写也在考察范围内。我自己的习惯是变量名用有语义的短词关键步骤加一行注释说明意图而不是复述代码超过二十行的函数考虑拆。比如写滑动窗口我会在关键判断上注释左边界跳到重复字符的下一位而不是注释left 加一。前者说明意图后者说明的是代码本身没有信息增量。还有一点面试时不要写炫技语法。什么一行 reduce 嵌套三元表达式写起来爽面试官读起来费劲。清晰可读永远优先于短。4. 从零到白板可写的训练路径4.1 三阶段刷题法我给自己的安排和后来帮别人做计划时基本都按三个阶段走每个阶段的目标不一样不要混。第一阶段是模板期大概两到三周。目标不是会做是见过。按题型主线各挑三到五道经典题看解法、抄一遍、再默写一遍。这个阶段允许看答案重点是建立手感知道每类题的典型形态。此阶段不要死磕难题遇到半小时没思路的直接看解析效率优先。第二阶段是输出期大概四到六周。目标是能独立写出完整代码并且自己检查边界。做法是每道题先自己写十五分钟写不出来再看提示只看第一句提示不看完整答案再写十分钟。这个阶段要开始记录错题后面会说怎么记。第三阶段是表达期两到三周。目标是边写边讲。做法是拿手机录音模拟面试场景把思路从读题到复杂度分析完整讲一遍然后回放听。这个阶段最能暴露问题很多人写得出来但讲不清楚回放一听就知道卡在哪。三个阶段的时间分配不一定要严格照搬但顺序别乱。跳过模板期直接输出会因为基础题型的解法没见过而卡死跳过表达期直接去面试会在开口那一刻大脑空白。4.2 错题本的正确记法大部分人记错题就是抄一遍正确答案这个做法几乎没用。我的记法是记三样东西缺一不可第一记卡点不是记答案。比如这题卡在没想到用哈希表存下标而不是抄代码。第二记变形方向。这道题如果改成求所有解、改成流式输入、改成数据有序解法要怎么调。这是把一道题变成一类题的关键。第三记一句话心法。用自己的话总结这道题的本质比如滑动窗口的关键是左边界什么时候动。以后复习只看心法能想起来就不看代码。我自己的错题本里大概有六十多道题但真正反复看的只有十来条心法。这个比例挺正常的说明大部分题看一遍就够了真正需要巩固的是那几个反复卡住你的点。4.3 模拟面试怎么练一个人也能练关键是制造压力和不完整信息。第一种是限时白板练习。关掉编辑器补全用记事本或者纸上写限时二十分钟。没有语法高亮、没有自动补全写出来的代码往往比平时差很多但这才是接近真实的状态。第二种是自问自答。拿到一道题先不看题解自己问自己这道题的关键信息是什么暴力解长什么样能不能优化然后说出答案。把这三个问题回答清楚基本就是一次完整的面试表达。第三种是找人互练。如果有同学或同事一起准备互相出题、互相追问如果数据量变大呢如果要求原地修改呢这种追问的压迫感是最接近真实面试的。5. 面试现场从读题到收尾的动作拆解5.1 读题与澄清的三分钟面试官念完题不要立刻开始写。这三分钟用得好能省掉后面十分钟的返工。具体动作是复述一遍题目确认自己理解对了问清楚输入范围数据量多大、有没有负数、是否有序、能否重复问清楚输出格式返回下标还是返回值、要不要处理无解的情况。这几个问题问出来一方面避免理解偏差另一方面向面试官展示你在考虑问题边界是加分项。有个细节值得注意如果题目描述有明显歧义问出来比猜要强。我见过候选人猜错了方向写了十五分钟才发现理解偏了时间基本就浪费了。5.2 卡壳时的自救话术卡住是常事关键是别沉默。沉默超过三十秒面试官会开始怀疑你是不是不会。这时候可以说我目前的思路是 X复杂度是 O(n²)我先写一个能跑的版本再看能不能优化。这句话的作用是把卡住转化成有保底方案。另一个技巧是先写例子。拿一个小例子在纸上手动推一遍往往就能发现问题所在。尤其是 DP 和递归题手动推两三步状态转移就清楚了。如果实在没思路可以诚实地说这个方向我想到的是 X但感觉有问题想确认一下是不是应该从 Y 角度考虑。面试官通常会给你一个方向提示顺着提示走比硬撑要好。不少人担心承认不会就挂了实际上在真实面试里能接住提示并推进本身就是一种能力体现。5.3 收尾阶段别浪费代码写完很多人就直接说我写完了。这个收尾其实还能拿分。标准动作是这样的自己口头走一遍测试用例至少包括一个正常用例、一个边界用例然后说一遍复杂度最后主动提一句可能的优化方向哪怕不实现。这三步做完一道题的完成度就很高了。如果时间还有富余可以主动说这里我想再确认一下如果输入是空数组我的代码会走到这行返回空数组符合预期。这种自我验证的动作是资深和初级的明显分野。6. 高频踩坑清单和我自己的几条经验6.1 五类最容易翻车的场景第一类是 JS 特性相关的坑。比如0.1 0.2 ! 0.3涉及浮点数比较的题目一定要用差值判断而不是等于。再比如sort()默认按字符串排序[1, 10, 2].sort()结果是[1, 10, 2]必须传比较函数。这类坑不涉及算法但一旦犯了面试官会觉得你 JS 基础不扎实。第二类是let和闭包的结合。循环里注册回调用var会全部拿到最后一个值用let天然解决。这个点被问的频率极高属于必答项。第三类是数组方法的副作用。splice会改原数组slice不会sort会改原数组filter不会。面试里如果需要不修改原数组用了splice就是错。第四类是递归的栈溢出。数据量大的时候递归深度过大会直接崩面试官追问数据量一百万条怎么办的时候要能答出改成迭代 手动维护栈。第五类是 Promise 相关。Promise.all遇到一个 reject 就整体 rejectPromise.allSettled会等所有完成。并发控制题里如果用了Promise.all去跑一批任务很可能不符合限制并发数的要求得自己实现一个调度器。6.2 我自己的几条体会第一条算法准备这件事量不重要连续性重要。每天两道题、坚持两个月效果远好于周末突击刷两百道。因为算法题的手感是用进废退的停一周就明显生疏。第二条面试前一定要过一遍自己写过的代码。我在面试里被问到你之前写的那个深拷贝如果遇到 Map 怎么办当场卡了。后来我形成了一个习惯面试前一天把所有手写题的最终版本过一遍重点看自己当时标注的坑。第三条不要为了显得厉害而写复杂解法。有次一个候选人用了个很花哨的解法面试官问能解释一下为什么这里不会重复访问吗他答不上来。简单清晰的解法加清楚的解释永远比复杂解法加分。第四条把算法题和业务场景联系起来讲。同样是讲 LRU 缓存如果能顺带说一句这个在前端缓存接口数据、限制本地存储体积的时候用得上面试官对你的印象会从会做题变成能落地。这一点我觉得是前端背景的人最该利用的优势。第五条面试是双向的。如果一家公司的算法题明显是后端题库直接搬过来的和岗位职责毫无关系那也说明他们对前端岗位的理解可能有问题。判断标准其实很简单面试官会不会顺着你的解法追问工程上的延伸会追问的通常是真的在用算法筛人只对答案的大概率是在走流程。最后分享一个我自己用的小方法准备一个只有一页的考前速览上面只放三类东西——六条题型主线的关键词、五个高频手写题的骨架防抖、深拷贝、并发控制、层序遍历、滑动窗口、以及那五个边界检查字空一重极异。面试前十分钟看一遍比临时翻题解有用得多。这个方法我用了几次进步不一定明显但至少能保证发挥稳定不会因为紧张而在基础题上翻车。