写在前面
说起来级联数据改造,估计很多小伙伴都不陌生,这个问题在面试当中真的很容易问到,之前我好几个朋友在面试过程中就考察了这道笔试题。
因为这道面试题其实挺经典的,不管是后端生成路由表还是级联数据都会涉及到,从这道面试题中可以反映出很多问题,接下来我将举例并讲解这道题的解题思路,希望可以帮助到大家,不管是在面试还是工作当中都可以借助相同的思路去解决这类问题!
举例
我们先来看一下案例和要求吧!感兴趣的小伙伴不妨自己先来试试!这道题的解题思路有很多种,可以分享你觉得最好的解题思路哈,一起来探讨!
改造前:
js
let arr = [
{ id: 10, name: '部门5', pid: 8 },
{ id: 2, name: '部门2', pid: 1 },
{ id: 8, name: '部门4', pid: 3 },
{ id: 12, name: '部门1', pid: 11 },
{ id: 1, name: '部门1', pid: 0 },
{ id: 3, name: '部门3', pid: 1 },
{ id: 11, name: '部门1', pid: 0 },
]
改造后:
js
[
{
"id": 11,
"name": "部门1",
"pid": 0,
"children": [
{
"id": 12,
"name": "部门1",
"pid": 11,
"children": []
}
]
},
{
"id": 1,
"name": "部门1",
"pid": 0,
"children": [
{
"id": 2,
"name": "部门2",
"pid": 1,
"children": []
},
{
"id": 3,
"name": "部门3",
"pid": 1,
"children": [
{
"id": 8,
"name": "部门4",
"pid": 3,
"children": [
{
"id": 10,
"name": "部门5",
"pid": 8,
"children": []
}
]
}
]
}
]
}
]
解题
接下来我们开始解题,我会保证整个题解的时间复杂度为 O(n)
,如果 双层 for 循环嵌套将会 是O(n²)
,什么是时间复杂度
?大家可以参考这篇文章。
(算法入门)人人都能看懂的时间复杂度和空间复杂度 - 掘金 (juejin.cn)
首先我们可以看到下面这个数组对象的数据
观察后得出 pid:0 为最顶层
id 是唯一的
pid 需要放在相对应的 id 下面
值得一提的是该数据是不规则的
子级可能在父级前面,父级可能在子级前面
所以我们需要在不知道顺序的情况下进行改造
所以接下来的思路既适用于有顺序还适用无顺序的情况
js
let arr = [
{ id: 10, name: '部门5', pid: 8 },
{ id: 2, name: '部门2', pid: 1 },
{ id: 8, name: '部门4', pid: 3 },
{ id: 12, name: '部门1', pid: 11 },
{ id: 1, name: '部门1', pid: 0 },
{ id: 3, name: '部门3', pid: 1 },
{ id: 11, name: '部门1', pid: 0 },
]
在这之前为了帮助大家更好的理解,我们先来一个小练习,请问下面的 a 最后是多少?
js
let a = { children: [] }
let b = {
children: [],
}
a.children.push(b)
b.children.push({ d: '123' })
console.log(a)
答案是
js
{
"children": [
{
"children": [
{
"d": "123"
}
]
}
]
}
为什么呢?
原因是在复杂数据类型中,数组存储的是指针,也就是地址,会去指向当前元素的值,而复杂数据类型发生变化后,在其他用到的地方也会发生变化,这就是为什么有的时候我们需要深拷贝数据,就是不需要当前数据的变化影响到原数据,从而影响到其他用到该数据的地方。
好了,了解了上面的我们再来看这道题。
思路
这道题我们分为两部分,因为数据是没有顺序的,第一部分对数据进行改造,对原始数据添加 children,同时将数据顺序和 id 以键,值为索引的方式存放到 map 中,方便后面找到父级元素。
第一部分
我们将 id
设置为键
,因为 id 是唯一
的,将值
设置为索引
,后面通过 id 去找到元素的位置
进行设置元素。
js
let hashMap = {},
transfer = []
for (let i = 0; i < itemList.length; i++) {
hashMap[itemList[i].id] = i
itemList[i].children = []
}
hashMap 数据结构如下:
这样我们就可以根据 pid 找到对应的 id
,也知道了其在数组中的位置
,然后将其添加进去即可!
第二部分
分析一下这个代码,首先循环数组,判断是不是顶层元素(顶层元素的判断条也有其他的,大家需要根据具体条件去改,这里的是pid为0),如果是直接添加到 transfer 中!
如果不是,我们找到其父级元素在数组中的位置 ,然后添加到其 children 中即可。
js
for (let index = 0; index < itemList.length; index++) {
if (itemList[index].pid !== 0 && hashMap[itemList[index].id] !== undefined) {
itemList[hashMap[itemList[index].pid]].children.push(itemList[index])
} else {
transfer.push(itemList[index])
}
}
为什么如果不是顶级直接添加即可?
我们可以这样理解,这有几个元素 A,B,C,D,E
A是顶层元素,是 B 的父级,B 是 C 的父级,依次类推
如果我往 D 添加进去 E,C 添加 D,B 添加 C,A 添加 B,最终 A 是不是包含B,B 又包含 C,依次类推
虽然我们是一级一级的进行添加的,但是因为复杂数据类型的特性,我们在一级一级添加的同时,最终所有数据都会添加到 A 中
,也就是我们上面写的 transfer 中,虽然我们只添加一次 transfer,但是因为层级关系
,其实最后所有的数据都被添加到 transfer
中了。
总结
在级联数据改造中,还是离不开需要利用复杂数据类型的特性
,今天的案例也充分的利用了这一点,我们首先对数据进行改造,将数据以 id=>index 形式存储起来,然后通过 pid
找到 id
对应的位置添加进去即可,很好理解。
怎么样,这种类型的题,如果以这个思路去解答是不是很不错呢?当然也有其他解题的思路,欢迎大家在评论区交流分享,互相学习!
完整代码
js
function transferData(itemList) {
let hashMap = {},
transfer = []
for (let i = 0; i < itemList.length; i++) {
hashMap[itemList[i].id] = i
itemList[i].children = []
}
for (let index = 0; index < itemList.length; index++) {
if (itemList[index].pid !== 0 && hashMap[itemList[index].id] !== undefined) {
itemList[hashMap[itemList[index].pid]].children.push(itemList[index])
} else {
transfer.push(itemList[index])
}
}
return transfer
}