2024年前端最全【面试题】20个常见的前端算法题,你全都会吗?_前端算法面试题,2024年最新最新前端开发面试解答

总结一下

面试前要精心做好准备,简历上写的知识点和原理都需要准备好,项目上多想想难点和亮点,这是面试时能和别人不一样的地方。

还有就是表现出自己的谦虚好学,以及对于未来持续进阶的规划,企业招人更偏爱稳定的人。

万事开头难,但是程序员这一条路坚持几年后发展空间还是非常大的,一切重在坚持。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

为了帮助大家更好更高效的准备面试,特别整理了《前端工程师面试手册》电子稿文件。

前端面试题汇总

// 内层循环用于每一轮的数据比较
// 注意j的起始范围是 i + 1
for (let j = i + 1; j < arr.length; j++) {
  // 寻找最小值
  if (arr[j] < arr[index]) {
    // 保存最小值的下标
    index = j;
  }
}
// 如果 index 不是目前的头部元素,则交换两者
if (index !== i) {
  [arr[i], arr[index]] = [arr[index], arr[i]];
}

}
return arr;
}
复制代码


#### 8、插入排序


时间复杂度为`O(n²)`,稳定


![](https://img-blog.csdnimg.cn/img_convert/30548e9409be22be4d6159347031b022.webp?x-oss-process=image/format,png)



function insertSort(arr) {
// 从第 2 个元素开始遍历序列
for (let i = 1; i < arr.length; i++) {
let j = i;
//记录要插入的目标元素
let target = arr[j];
//从 target 所在位置向前遍历,直至找到一个比目标元素小的元素,目标元素插入到该元素之后的位置
while (j > 0 && arr[j - 1] > target) {
//移动前一个元素的位置,将其向后移动一个位置
arr[j] = arr[j - 1];
j–;
}
arr[j] = target;
}
return arr;
}
复制代码


#### 9、快速排序


时间复杂度为`O(nlogn)`,不稳定


![](https://img-blog.csdnimg.cn/img_convert/f8a77f85abc88af65f582946deed70e8.webp?x-oss-process=image/format,png)



function quickSort(list) {
// 当list.length <= 1时,退出递归
if (list.length <= 1) return list;
// 找到中间节点
let mid = Math.floor(list.length / 2);
// 以中间节点为基准点,比该节点大的值放到right数组中,否则放到left数组中
let base = list.splice(mid, 1)[0];
let left = [];
let right = [];
list.forEach(item => {
if (item > base) {
right.push(item);
} else {
left.push(item);
}
});
// 重新组合数组
return quickSort(left).concat(base, quickSort(right));
}
复制代码


#### 10、归并排序


时间复杂度为`O(nlogn)`,稳定


![](https://img-blog.csdnimg.cn/img_convert/5a781478815356cf374d065194d83384.webp?x-oss-process=image/format,png)



function MergeSort(array) {
let len = array.length;

// 当每个子序列中仅有1个元素时返回
if (len <= 1) {
return array;
}
// 将给定的列表分为两半
let num = Math.floor(len / 2);
let left = MergeSort(array.slice(0, num));
let right = MergeSort(array.slice(num, array.length));
return merge(left, right);

function merge(left, right) {
let [l, r] = [0, 0];
let result = [];
// 从 left 和 right 区域中各个取出第一个元素,比较它们的大小
while (l < left.length && r < right.length) {
// 将较小的元素添加到result中,然后从较小元素所在的区域内取出下一个元素,继续进行比较;
if (left[l] < right[r]) {
result.push(left[l]);
l++;
} else {
result.push(right[r]);
r++;
}
}
// 如果 left 或者 right 有一方为空,则直接将另一方的所有元素依次添加到result中
result = result.concat(left.slice(l, left.length));
result = result.concat(right.slice(r, right.length));
return result;
}
}
复制代码


#### 11、列表转成树


**题目**: 输入一组列表如下,转化成树形结构


**输入**:



[
{ id: 1, title: “child1”, parentId: 0 },
{ id: 2, title: “child2”, parentId: 0 },
{ id: 3, title: “child1_1”, parentId: 1 },
{ id: 4, title: “child1_2”, parentId: 1 },
{ id: 5, title: “child2_1”, parentId: 2 }
]
复制代码


**输出**:



tree=[
{
“id”: 1,
“title”: “child1”,
“parentId”: 0,
“children”: [
{
“id”: 3,
“title”: “child1_1”,
“parentId”: 1
},
{
“id”: 4,
“title”: “child1_2”,
“parentId”: 1
}
]
},
{
“id”: 2,
“title”: “child2”,
“parentId”: 0,
“children”: [
{
“id”: 5,
“title”: “child2_1”,
“parentId”: 2
}
]
}
]
复制代码



function listToTree(data) {
// 使用对象重新存储数据, 空间换时间
let map = {};
// treeData存储最后结果
let treeData = [];
// 遍历原始数据data,存到map中,id为key,值为数据
for (let i = 0; i < data.length; i++) {
map[data[i].id] = data[i];
}
// 遍历对象
for (let i in map) {
// 根据 parentId 找到的是父节点
if (map[i].parentId) {
if (!map[map[i].parentId].children) {
map[map[i].parentId].children = [];
}
// 将子节点放到父节点的 children中
map[map[i].parentId].children.push(map[i]);
} else {
// parentId 找不到对应值,说明是根结点,直接插到根数组中
treeData.push(map[i]);
}
}
return treeData;
}
复制代码


#### 12、深度优先遍历


**题目**: 对树进行遍历,从第一个节点开始,遍历其子节点,直到它的所有子节点都被遍历完毕,然后再遍历它的兄弟节点


**输入**: 上文第11题生成的tree


**输出**: [1, 3, 4, 2, 5]



// 递归版本
function deepTree(tree, arr = []) {
if (!tree || !tree.length) return arr;
tree.forEach(data => {
arr.push(data.id);
// 遍历子树
data.children && deepTree(data.children, arr);
});
return arr;
}

// 非递归版本
function deepTree(tree) {
if (!tree || !tree.length) return;
let arr = [];
let stack = [];
//先将第一层节点放入栈
for (let i = 0, len = tree.length; i < len; i++) {
stack.push(tree[i]);
}
let node;
while (stack.length) {
// 获取当前第一个节点
node = stack.shift();
arr.push(node.id);
//如果该节点有子节点,继续添加进入栈顶
if (node.children && node.children.length) {
stack = node.children.concat(stack);
}
}
return arr;
}
复制代码


#### 13、广度优先遍历


**题目**: 以横向的维度对树进行遍历,从第一个节点开始,依次遍历其所有的兄弟节点,再遍历第一个节点的子节点,一层层向下遍历


**输入**: 上文第11题生成的tree


**输出**: [1, 2, 3, 4, 5]



function rangeTree(tree) {
if (!tree || !tree.length) return;
let arr = [];
let node, list = […tree];
// 取出当前节点
while ((node = list.shift())) {
arr.push(node.id);
node.children && list.push(…node.children);
}
return arr;
}
复制代码


#### 14、树形结构查找节点


**题目**: 查找树形结构中符合要求的节点


**输入**:  
 tree: 上文第11题生成的tree  
 func: data => data.title === "child2\_1"


**输出**:{ id: 5, parentId: 2, title: "child2\_1" }



/**

  • 查找节点
  • @param {array} tree - 树
  • @param {function} func - 查找条件
  • */
    function findTreeNode(tree, func) {
    for (const data of tree) {
    // 条件成立 直接返回
    if (func(data)) return data;
    if (data.children) {
    const res = findTreeNode(data.children, func);
    // 结果存在再返回
    if (res) return res;
    }
    }
    return null;
    }
    // 测试
    findTreeNode(tree, data => data.title === “child2_1”)
    复制代码

#### 15、二叉查找树


**题目**: 判断一个数组,是否为某二叉查找树的前序遍历结果,二叉查找树特点是所有的左节点比父节点的值小,所有的右节点比父节点的值大


**输入**: [5, 3, 2, 1, 4, 6, 7, 8, 9]


**输出**: true



function preOrderOfBST(list) {
if (list && list.length > 0) {
// 前序遍历,第一个值为根节点
var root = list[0];
// 找到数组中,第一个比根节点大的节点,即为右子树的节点
for (var i = 0; i < list.length; i++) {
if (list[i] > root) {
break;
}
}
// 遍历右子树的节点,要求所有右子树的节点都比根节点大
for (let j = i; j < list.length; j++) {
if (list[j] < root) {
return false;
}
}
var left = true;
// 同理,递归判断左子树是否符合二叉查找树的规则
if (i > 1) {
left = preOrderOfBST(list.slice(1, i + 1));
}
var right = true;
// 递归判断右子树是否符合二叉查找树的规则
if (i < list.length) {
right = preOrderOfBST(list.slice(i, list.length));
}
// 左、右子树都符合要求,则是一个二叉查找树
return left && right;
}
}
复制代码


#### 16、查找二叉树的路径


**题目**: 查找二叉树和为某一值的路径


**输入**: 二叉树结构如下,找到和为 11 的所有路径


![](https://img-blog.csdnimg.cn/img_convert/57b16ece7733261b1586d25b62ca7e41.webp?x-oss-process=image/format,png)


**输出**: [[5, 3, 2, 1], [5, 6]]



/**

  • 利用回溯算法,找到和为某一值的路径

  • @param {object} node - 二叉树

  • @param {number} num - 目标值

  • @param {array} stack - 栈

  • @param {number} sum - 当前路径的和

  • @param {array} result - 存储所有的结果

  • */
    function findPath(node, num, stack = [], sum = 0, result = []) {
    stack.push(node.data);
    sum += node.data;

    // 找到所有的节点路径(包含叶子节点和子节点的所有情况之和)
    if (sum === num) {
    // if (!node.left && !node.right && sum === num) { // 找到所有的叶子节点路径
    result.push(stack.slice());
    }
    if (node.left) {
    findPath(node.left, num, stack, sum, result);
    }
    if (node.right) {
    findPath(node.right, num, stack, sum, result);
    }
    // 回溯算法:不符合要求,退回来,换一条路再试
    // 叶子节点直接pop;子节点中的所有的节点递归完成后再pop
    stack.pop();
    return result;
    }
    复制代码


#### 17、买卖股票问题


**题目**: 给定一个整数数组,其中第 `i` 个元素代表了第 `i`天的股票价格;  
 非负整数 `fee` 代表了交易股票的手续费用,求返回获得利润的最大值


**输入**: arr: [1, 12, 13, 9, 15, 8, 6, 16]; fee: 2


**输出**: 22



/**

  • 贪心算法求解
  • @param {array} list - 股票每天的价格列表
  • @param {number} fee - 手续费
  • */
    function buyStock(list, fee) {
    // min为当前的最小值,即买入点
    let min = list[0],
    sum = 0;
    for (let i = 1; i < list.length; i++) {
    // 从1开始,依次判断
    if (list[i] < min) {
    // 寻找数组的最小值
    min = list[i];
    } else {
    // 计算如果当天卖出是否赚钱
    let temp = list[i] - min - fee;
    if (temp > 0) {
    // 赚钱 存数据
    sum += temp;
    // 关键代码:重新计算min,分两种情况,如果后面继续涨,则默认继续持有;若后面跌,则以后面的价格重新买入
    min = list[i] - fee;
    }
    }
    }
    return sum;
    }
    复制代码

#### 18、斐波那契数列


**题目**: 从第3项开始,当前项等于前两项之和: `1 1 2 3 5 8 13 21 ……`,计算第n项的值


**输入**: 10


**输出**: 89



// 使用动态规划,将复杂的问题拆分,也就是:F(N) = F(N - 1) + F(N - 2),用数组将已经计算过的值存起来
function fib(n) {
// 使用dp数组,将之前计算的结果存起来,防止栈溢出
if (n < 2) return 1;
let dp = [1, 1];
for (let i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n - 1];
}
复制代码


#### 19、滑动窗口最大值


**题目**: 给定一个数组 `nums`,有一个大小为 `k` 的滑动窗口,从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口中的k个数字。滑动窗口每次只向右移动一位,求返回滑动窗口最大值


**输入**: nums: [1,3,-1,-3,5,3,6,7]; k: 3


**输出**: [3, 3, 5, 5, 6, 7]


![](https://img-blog.csdnimg.cn/img_convert/70dda7c12e45ba0b0a0f0abb08d65f0a.webp?x-oss-process=image/format,png)



function maxSlidingWindow(nums, k) {
// window存储当前窗口中数据的下标
const window = [];
// result存储窗口中的最大值
const result = [];
for (let i = 0; i < nums.length; i++) {
if (i - window[0] > k - 1) {
// 剔除窗口长度超出范围时左侧的最大值

最后

推荐一些系统学习的途径和方法。

路线图

每个Web开发人员必备,很权威很齐全的Web开发文档。作为学习辞典使用,可以查询到每个概念、方法、属性的详细解释,注意使用英文关键字搜索。里面的一些 HTML,CSS,HTTP 技术教程也相当不错。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

HTML 和 CSS:

html5知识

css基础知识

  • 10
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值