摘要:本文汇总前端面试中 95% 的高频手撕算法真题,覆盖数组、字符串、递归、动态规划、工程工具及模拟手写六大方向。每道题均配有业务场景、解题思路与可直接运行的完整代码,并附面试话术,帮助读者快速背诵默写、从容应对笔试与手撕环节。
前端面试算法手撕全解(高频真题+标准答案)
前言
前端面试算法不考困难算法、不考复杂数据结构 ,全部集中在:数组、字符串、递归、遍历、模拟、简单DP、工程工具手写 。本文档汇总95% 前端手撕真题,每道题包含:场景、思路、完整可运行代码、复杂度分析、面试话术,可直接背诵默写。
阅读建议:先通读「一、面试特点」建立全局认知,再按「九、刷题优先级」的顺序逐题攻克。每道题建议先自己手写一遍,再对照标准答案查漏补缺,最后用「八、四段式答题法」口头复述一遍,做到"写得对、讲得清"。
一、前端算法面试特点(必看)
-
重点:实用性算法,全部来自业务开发 + 笔试真题
-
核心能力:遍历、递归、条件判断、数据格式化、边界处理
-
手撕评分标准:代码完整、无BUG、能处理边界、能讲清思路、知道业务场景
二、数组高频算法(最常考)
解题思路 :利用 Set 天然去重特性,一行代码搞定;filter + indexOf 通过判断元素首次出现位置是否等于当前索引来去重,原理清晰;Map 写法利用 map.has() 判断是否已存在,效率更高。
复杂度分析:三种写法时间复杂度均为 O(n),空间复杂度均为 O(n)。
面试话术:"去重我一般先答 Set 最简写法,再补充 filter + indexOf 讲原理,最后用 Map 体现对引用类型和效率的考虑,形成梯度回答。"
解题思路:遍历数组,遇到子数组就递归展开并拼接结果,遇到普通元素直接 push,直到所有层级都被展开。
复杂度分析:时间复杂度 O(n),其中 n 为展开后所有元素的个数;空间复杂度 O(n)(递归调用栈深度取决于嵌套层级)。
面试话术 :"扁平化考察递归思想,我会先写递归版本,再提一句可以用 arr.flat(Infinity) 一行实现,体现对原生 API 的熟悉。"
解题思路 :求和用 reduce 累加,简洁且性能好;最大值用 Math.max 配合展开运算符,注意空数组会返回 -Infinity,可加默认值兜底。
复杂度分析:两者时间复杂度均为 O(n),空间复杂度 O(1)。
面试话术 :"求和与最大值是 reduce 和展开运算符的经典应用,我会顺带提一句边界处理------空数组时 Math.max(...[]) 返回 -Infinity,实际开发要加默认值。"
解题思路 :用 Map 记录已出现过的 key 值,filter 遍历时只保留第一次出现的元素,实现按指定字段去重。
复杂度分析:时间复杂度 O(n),空间复杂度 O(n)。
面试话术 :"对象去重是业务里最高频的场景,我会强调用 Map 而不是 Set,因为 Map 可以按任意字段(如 id)去重,更灵活。"
三、字符串高频算法
1. 字符串反转
场景:笔试基础题、密码反转、文本处理
javascript
function reverseStr(str) {
return str.split('').reverse().join('');
}
2. 回文字符串判断
定义:正读、反读完全一致
javascript
function isPalindrome(str) {
return str === reverseStr(str);
}
3. 统计字符串出现最多字符
场景:文本统计、关键词分析
javascript
function getMaxChar(str) {
const obj = {};
let maxNum = 0;
let maxChar = '';
for (let char of str) {
obj[char] = obj[char] ? obj[char] + 1 : 1;
if (obj[char] > maxNum) {
maxNum = obj[char];
maxChar = char;
}
}
return { maxChar, maxNum };
}
四、递归经典面试题(前端必考)
1. 递归深拷贝(终极手写)
业务场景:复杂表单数据备份、树形结构复制、状态缓存、防止原数据污染
面试亮点:处理基础类型、数组、对象、日期、正则,边界齐全
javascript
function deepClone(obj) {
// 1. 基础类型 / null 直接返回
if (obj === null || typeof obj !== 'object') return obj;
// 2. 处理特殊引用类型
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
// 3. 创建对应容器
const newObj = Array.isArray(obj) ? [] : {};
// 4. 递归遍历
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = deepClone(obj[key]);
}
}
return newObj;
}
2. 递归遍历树形结构
场景:后台菜单、权限树、分类树、树形筛选
javascript
// 遍历树形数据,执行回调
function traverseTree(list, callback) {
list.forEach(item => {
callback(item);
if (item.children && item.children.length) {
traverseTree(item.children, callback);
}
});
}
五、动态规划 & 笔试经典题
1. 最大连续子数组和(LeetCode简单经典)
场景:前端笔试高频压轴简单题
javascript
function maxSubArray(nums) {
let max = nums[0];
let sum = 0;
for (let i = 0; i < nums.length; i++) {
// 要么单独当前数,要么累加之前
sum = Math.max(sum + nums[i], nums[i]);
max = Math.max(max, sum);
}
return max;
}
2. 斐波那契数列(优化版)
考点:递归、缓存优化、避免爆栈
javascript
// 迭代优化(推荐,无栈溢出)
function fib(n) {
if (n <= 2) return 1;
let a = 1, b = 1;
for (let i = 3; i <= n; i++) {
[a, b] = [b, a + b];
}
return b;
}
六、前端工程算法(面试必手撕)
1. 防抖函数
场景:搜索框联想、输入框校验、窗口 resize、表单实时校验
核心:频繁触发重置计时,只执行最后一次
javascript
function debounce(fn, delay = 500) {
let timer = null;
return function(...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
timer = null;
}, delay);
}
}
2. 节流函数
场景:按钮防重提交、滚动加载、轮播、高频点击
核心:固定时间内只执行一次
javascript
function throttle(fn, delay = 1000) {
let flag = true;
return function(...args) {
if (!flag) return;
flag = false;
setTimeout(() => {
fn.apply(this, args);
flag = true;
}, delay);
}
}
七、高频模拟手写(伪算法,前端超高频)
1. 手写 call / apply / bind
javascript
// 手写call
Function.prototype.myCall = function(context, ...args) {
context = context || window;
context.fn = this;
const res = context.fn(...args);
delete context.fn;
return res;
};
// 手写apply
Function.prototype.myApply = function(context, args = []) {
context = context || window;
context.fn = this;
const res = context.fn(...args);
delete context.fn;
return res;
};
// 手写bind
Function.prototype.myBind = function(context, ...args) {
return (...newArgs) => this.myCall(context, ...args, ...newArgs);
};
2. 手写 Promise 简易版
javascript
class MyPromise {
constructor(executor) {
this.status = 'pending';
this.value = null;
this.reason = null;
this.onFulfilled = [];
this.onRejected = [];
const resolve = (val) => {
if (this.status !== 'pending') return;
this.status = 'fulfilled';
this.value = val;
this.onFulfilled.forEach(fn => fn());
};
const reject = (err) => {
if (this.status !== 'pending') return;
this.status = 'rejected';
this.reason = err;
this.onRejected.forEach(fn => fn());
};
executor(resolve, reject);
}
then(resolveFn, rejectFn) {
if (this.status === 'fulfilled') resolveFn(this.value);
if (this.status === 'rejected') rejectFn(this.reason);
if (this.status === 'pending') {
this.onFulfilled.push(() => resolveFn(this.value));
this.onRejected.push(() => rejectFn(this.reason));
}
}
}
八、算法面试通用答题模板(背诵)
四段式答题法
-
题意分析:简单说明题目要实现的效果
-
解题思路:遍历/递归/双指针/动态规划
-
核心关键点:边界处理、终止条件、去重、状态保存
-
业务场景:对应项目中真实使用位置
九、前端算法刷题优先级(速成顺序)
-
防抖、节流、深拷贝 (必问)
-
数组去重、扁平化、对象去重 (最高频)
-
字符串操作、递归树遍历 (常规笔试)
-
简单DP、斐波那契、最大子数组 (拔高笔试)
-
手写call/apply/bind/Promise (中高级必问)