从零到一:彻底搞定面试高频算法——“列表转树”与“爬楼梯”全解析

在前端面试中,算法往往是决定能否拿高薪的关键。很多同学一听到"算法"就头大,觉得那是天才玩的游戏。其实,大多数面试算法题考察的不是你的数学造诣,而是你对递归(Recursion)和逻辑处理的理解。

今天,我们就通过两个非常经典的面试真题------ "列表转树(List to Tree)"和"爬楼梯(Climbing Stairs)" ,带你从小白视角拆解算法的奥秘。

第一部分:列表转树 ------ 业务中的"常青树"

1. 为什么要学这个?

在实际开发中,后端返回给我们的数据往往是"扁平化"的。比如一个省市区选择器,或者一个后台管理系统的左侧菜单导航。为了存储方便,数据库通常会存储为如下结构:

id parentId name
1 0 中国
2 1 北京
3 1 上海
4 2 东城区

但前端 UI 组件(如 Element UI 的 Tree 组件)需要的是一个嵌套的树形对象 。如何把上面的表格数据转换成包含 children 的树?这就是面试官考察你的数据结构处理能力。

2. 解法一:暴力递归(最符合人类直觉)

核心逻辑:

  1. 遍历列表,找到根节点(parentId === 0)。
  2. 对于每一个节点,再去列表里找谁的 parentId 等于我的 id
  3. 递归下去,直到找不到子节点。
JavaScript 复制代码
// 代码参考
function list2tree(list, parentId = 0) {
  const result = []; 
  list.forEach(item => {
    if (item.parentId === parentId) {
      // 这里的递归就像是在问:谁是我的孩子?
      const children = list2tree(list, item.id);
      if (children.length) {
        item.children = children;
      }
      result.push(item);
    }
  });
  return result;
}

小白避坑指南:

这种方法的复杂度是 O ( n 2 ) O(n^2) O(n2)。如果列表有 1000 条数据,最坏情况下要跑 100 万次循环。面试官此时会问:"有没有更优的方法?"

3. 解法二:优雅的 ES6 函数式写法

如果你想让代码看起来更"高级",可以利用 filtermap

JavaScript 复制代码
function list2tree(list, parentId = 0) {
  return list
    .filter(item => item.parentId === parentId) // 过滤出当前的子节点
    .map(item => ({
      ...item, // 展开原有属性
      children: list2tree(list, item.id) // 递归寻找后代
    }));
}

4. 解法三:空间换时间(面试官最爱)

为了把时间复杂度降到 O ( n ) O(n) O(n),我们可以利用 Map 对象。Map 的查询速度极快,像是一个"瞬移器"。

思路:

  1. 先遍历一遍列表,把所有节点存入 Map 中,以 id 为 Key。
  2. 再遍历一遍,根据 parentId 直接从 Map 里把父节点"揪"出来,把当前节点塞进父节点的 children 里。
JavaScript 复制代码
// 代码参考
function listToTree(list) {
    const nodeMap = new Map();
    const tree = [];

    // 第一遍:建立映射表
    list.forEach(item => {
        nodeMap.set(item.id, { ...item, children: [] });
    });

    // 第二遍:建立父子关系
    list.forEach(item => {
        const node = nodeMap.get(item.id);
        if (item.parentId === 0) {
            tree.push(node); // 根节点入队
        } else {
            // 直接通过 parentId 找到父亲,把儿子塞进去
            nodeMap.get(item.parentId)?.children.push(node);
        }
    });
    return tree;
}

优点: 只遍历了两遍列表。无论数据有多少,速度依然飞快。

第二部分:爬楼梯 ------ 掌握算法的"分水岭"

如果说"列表转树"考察的是业务能力,那"爬楼梯"考察的就是编程思维

题目描述: 假设你正在爬楼梯。需要 n n n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

1. 自顶向下:递归的艺术

我们站在第 n n n 阶往回看:

  • 要到达第 10 阶,你只能从第 9 阶跨 1 步上来,或者从第 8 阶跨 2 步上来。
  • 所以: f ( 10 ) = f ( 9 ) + f ( 8 ) f(10) = f(9) + f(8) f(10)=f(9)+f(8)。

这就是著名的斐波那契数列公式

JavaScript 复制代码
// 基础版
function climbStairs(n) {
    if (n === 1) return 1;
    if (n === 2) return 2;
    return climbStairs(n - 1) + climbStairs(n - 2);
}

致命缺陷: 这个代码会跑死电脑。算 f ( 10 ) f(10) f(10) 时要算 f ( 9 ) f(9) f(9) 和 f ( 8 ) f(8) f(8);算 f ( 9 ) f(9) f(9) 时又要算一遍 f ( 8 ) f(8) f(8)。大量的重复计算导致"爆栈"。

2. 优化:带备忘录的递归(记忆化)

我们可以准备一个"笔记本"(memo),算过的值就记下来,下次直接拿。

JavaScript 复制代码
const memo = {};
function climbStairs(n) {
    if (n === 1) return 1;
    if (n === 2) return 2;
    if (memo[n]) return memo[n]; // 翻翻笔记,有就直接给
    memo[n] = climbStairs(n - 1) + climbStairs(n - 2);
    return memo[n];
}

3. 自底向上:动态规划(DP)

动态规划(Dynamic Programming)听起来很高大上,其实就是倒过来想。

我们不从 f ( n ) f(n) f(n) 往回找,而是从 f ( 1 ) f(1) f(1) 开始往后推:

  • f ( 1 ) = 1 f(1) = 1 f(1)=1
  • f ( 2 ) = 2 f(2) = 2 f(2)=2
  • f ( 3 ) = 1 + 2 = 3 f(3) = 1 + 2 = 3 f(3)=1+2=3
  • f ( 4 ) = 2 + 3 = 5 f(4) = 2 + 3 = 5 f(4)=2+3=5
JavaScript 复制代码
function climbStairs(n) {
  if (n <= 2) return n;
  const dp = new Array(n + 1);
  dp[1] = 1;
  dp[2] = 2;
  for (let i = 3; i <= n; i++) {
    dp[i] = dp[i - 1] + dp[i - 2]; // 每一个结果都是前两个的和
  }
  return dp[n];
}

4. 极致优化:滚动变量

既然 f ( n ) f(n) f(n) 只依赖前两个数,那我们连数组都不需要了,只需要三个变量在手里"滚"起来。

JavaScript 复制代码
function climbStairs(n) {
    if(n <= 2) return n;
    let prePrev = 1; // f(n-2)
    let prev = 2;    // f(n-1)
    let current;
    for(let i = 3; i <= n; i++){
        current = prev + prePrev;
        prePrev = prev;
        prev = current;
    }
    return current;
}

这时的空间复杂度降到了 O ( 1 ) O(1) O(1),几乎不占用额外内存。

总结:小白如何精进算法?

通过这两道题,你应该能发现算法学习的规律:

  1. 先画图,后写码: 不管是树状结构还是楼梯台阶,画出逻辑图比直接写代码重要得多。
  2. 寻找重复子问题: 递归和 DP 的核心都在于把大问题拆解成一样的小问题。
  3. 从暴力到优化: 别指望一步写出最优解。先用最笨的方法写出来,再去思考如何减少重复计算。
相关推荐
kkeeper~18 分钟前
0基础C语言积跬步之数据在内存中的存储
c语言·数据结构·算法
小江的记录本33 分钟前
【JVM虚拟机】垃圾回收GC:垃圾收集器:CMS:核心原理、回收流程、优缺点、废弃原因(附《思维导图》+《面试高频考点清单》)
java·jvm·后端·python·spring·面试·maven
wabs6662 小时前
关于贪心算法的一些自我总结【力扣45.跳跃游戏II】【灵感来源:代码随想录】
算法·贪心算法·复盘
2401_876964132 小时前
【湖北专升本】2026湖北专升本真题PDF+备考资料汇总
数据结构·人工智能·经验分享·深度学习·算法·计算机视觉
zhangyao9403302 小时前
开发pc端时,表格的高度怎么设置才能铺满页面
前端·javascript·elementui
嗝o゚2 小时前
CANN GE 算子融合——融合算法与调度策略
算法·昇腾·cann·ge
小江的记录本3 小时前
【JVM虚拟机】垃圾回收GC:垃圾回收算法:标记-清除、标记-复制、标记-整理、分代收集(附《思维导图》+《面试高频考点清单》)
java·jvm·后端·python·算法·安全·面试
XinZong3 小时前
实测OpenClaw虾淘:全民工具AI时代,冷门非工具类的Skill还能出圈吗?
javascript
烛衔溟3 小时前
TypeScript 类的类型 —— 作为类型使用
javascript·ubuntu·typescript
之歆3 小时前
Day16_JavaScript 轮播图与事件工程实战(下篇)
服务器·开发语言·前端·javascript·网络·性能优化