当前位置: 首页 > news >正文

前端高频面试手写题——扁平化数组转树

题目背景:

后端经常返回扁平列表,如部⻔、评论、菜单等,需要在前端转成树形结构渲染。

任务:

请写⼀个通⽤函数arrayToTree,把平铺数组转换成树。

输入:

type Item = { id: number; parentId: number | null; [key: string]: any
}

例如:

const list: Item[] = [{ id: 1, parentId: null, name: 'Root' },{ id: 2, parentId: 1, name: 'Child 1' },{ id: 3, parentId: 1, name: 'Child 2' },{ id: 4, parentId: 2, name: 'GrandChild 1' },
];
// 输出格式参考:
interface TreeNode extends Item {children: TreeNode[];
}// 签名函数:
export function arrayToTree<T extends { id: number;parentId: number | null }>(items: T[]
): (T & { children: T[] })[] { ... }// 示例调用
const tree = arrayToTree(list);
console.log(tree);
// =>
// [{
// id: 1,
// parentId: null,
// name: 'Root',
// children: [
// { id: 2, parentId: 1, name: 'Child 1', children: [...] },
// ...

初始定义:

type Item = {id: number;parentId: number | null;[key: string]: any
}
interface TreeNode extends Item {children: TreeNode[];
}
const list: Item[] = [{ id: 1, parentId: null, name: 'Root' },{ id: 2, parentId: 1, name: 'Child 1' },{ id: 3, parentId: 1, name: 'Child 2' },{ id: 4, parentId: 2, name: 'GrandChild 1' },
];

方法1(暴力):

分析数组中的信息:

  • item:本身的id,父节点id,自身name
  • 根节点父节点为null
  • 父节点id值一致的item,属于同一个父节点,互为兄弟节点,在同一级

原始的list像是一条一条的记录,转换成树结构后,变成像是竖着的一级一级的思维导图式的形式

如何转换?
遍历list
如何处理list中的item?

  • 将item生成一个TreeNode节点
  • 通过第一个节点的parentId ,找到对应的item
  • 将这些item转化成TreeNode节点,添加到第一个节点中
  • 重复此操作,依此处理第一个节点的children数组
  • 记录对应的parentId,遍历list,将item添加到对应节点的children数组中,
const arr: TreeNode[] = [];
arr.push({...list[0],children: []
})
function arrayToTree(arr){for(let i = 0; i < arr.length; i++){let id = arr[i].id;for(let j = 0; j < list.length ; j++){if(list[j].parentId === id){arr[i].children.push({...list[j],children: []});}}arrayToTree(arr[i].children);}
}
// 使用
arrayToTree(arr);

缺陷

  1. 这个方法手动指定了一个根节点,如果有多个根节点,这个写法就不适配了,因此我们需要自动将根节点加入进去
function initArr() {list.filter(item => item.parentId === null).map(item => {arr.push({...item,children: []})})
}
initArr();
  1. 虽然好理解,但是效率实在是太低,退退退🤺,out。

方法1的简化版:

function arrayToTree(list, parentId = null) {return list.filter(item => item.parentId === parentId).map(item => ({...item,children: arrayToTree(list, item.id)}));
}
arrayToTree(list, null);

思路:

  1. 先将parentId一致的节点过滤,再依此处理符合条件的item,将item转换成TreeNode,通过return的方式添加到父节点中
  2. 递归处理每个节点

缺陷:

  1. 虽然写法简单,通俗易懂,但效率还是太低了,时间复杂度 O(n²),数据一大就不行了, out。

方法2(map)

  • 将list中所有的item,都转换为TreeNode节点并存在map中
  • 遍历list,处理每个item:
    1. 遇到根节点(parentId === null),加入roots中
    2. 遇到其他节点,通过map找到该item的parentId对应的映射(父节点),将该item加入到该父节点的chrilren中
function arrayToTree(list) {const map = new Map();const roots = [];// 初始化 maplist.forEach(item => {map.set(item.id, { ...item, children: [] });});// 构建父子关系list.forEach(item => {const node = map.get(item.id);if (item.parentId === null) {roots.push(node);} else {const parent = map.get(item.parentId);if (parent) {parent.children.push(node);}}});return roots;
}
arrayToTree(list);
  1. 时间复杂度O(n), 推荐。

终极版(ts泛型 + 类型推导):

这个版本我们加上了ts泛型,进行了类型约束

首先我们需要明白类型约束的含义:

export function arrayToTree<T extends { id: number;parentId: number | null }>(items: T[]
): (T & { children: T[] })[] { ... }
// 解释:
// arrayToTree<泛型约束>(参数):(返回值){}
// <T extends { id: number; parentId: number | null }>: T为泛型约束,T中必须包含两个字段:id和parentId
// (items: T[]): 函数参数items, 类型是一个T类型的数组
// :(T & { children: T[] })[] : 函数返回值,返回值是一个数组,数组中的元素必须包含:
// 1. T中的id和parentId
// 2. 新增了children属性,children属性是T类型的数组

完整代码:

function arrayToTree<T extends {id: number;parentId: number | null
}>(items: T[]
): (T & { children: T[] })[] {const map = new Map();const result: (T & { children: T[]})[] = [];// 初始化maplist.forEach(item => {map.set(item.parentId, { ...item, children: [] });})// 转化list.forEach(item => {const node = map.get(item.id);if (item.parentId === null) {result.push(node)} else {const parent = map.get(item.parentId);if (parent) {parent.children.push(node);}}})return result;
}arrayToTree(list);

它和方法2的思路一致,都是使用map的方式,先建立id映射,在遍历list,找到对应的映射,进行处理,perfect!

http://www.dtcms.com/a/569831.html

相关文章:

  • 网站建设合同通用范本免费推广引流怎么做
  • 上海怎么建设网站网站建设网站制作公司
  • Flink 多流转换
  • Redis_5_单线程模型
  • 做简单网站用什么软件有哪些洛阳网站建设设计公司
  • CTF WEB入门 命令执行篇29-49
  • IDEA自定义类注释、方法注释
  • Grafana12安装部署[特殊字符]
  • 网站建设报价流程河南建设工程信息网站
  • 苍穹外卖(第五天)
  • NFC与RFID防伪标签:构筑产品信任的科技防线
  • 深圳网站建设 设计首选成都展示型网页设计公司
  • 网站三层结构示意图网站建设资讯
  • WithAnyone: Towards Controllable and ID Consistent Image Generation论文阅读
  • 无人机远距离无线通信模块:突破空中通信的未来之钥
  • IDEA:2020.1 下面有四个小版本:2020.1.1 -- 2020.1.4,哪个与Windows7 更兼容
  • 长春建站网站模板网站仿站
  • 【ROS2+相机】在Ubuntu安装realsense-ros
  • 基于B/S架构的物资管理系统的设计与实现(源码+论文+部署+安装)
  • 告别扫描仪!AI一键PBR材质
  • 网站建设公司内幕中企动力科技做什么的
  • AI代发货(DropShopping)革命:构建自动化电商帝国终极指南
  • 视频网站怎样做一元夺宝网站开发
  • 最近联系人-有点疑惑
  • RV1126 NO.37:OPENCV的图像叠加功能
  • 时序数据库系列(一):InfluxDB入门指南核心概念详解
  • 网站建设与维护工作电商网站开发prd
  • 东莞网站推广模式网站设计标杆企业
  • html 和css基础常用的标签和样式
  • 应用通知如何接入飞书