真实面经题目 · 原创解析
手撕:数组拉平(flatten)
这题考察递归遍历、depth 语义、顺序保持和边界处理。回答时要先约定函数契约,再写出不会丢类型的实现,并说明深层数组时的栈风险。
真实面经题目 · 原创解析
这题考察递归遍历、depth 语义、顺序保持和边界处理。回答时要先约定函数契约,再写出不会丢类型的实现,并说明深层数组时的栈风险。
数组拉平就是按原顺序遍历输入,遇到数组且当前 depth 还大于 0 就继续展开,否则把元素放进结果。实现时要明确 depth 默认值、depth 为 0 时保留当前结构、空数组如何处理,以及是否跳过稀疏数组空位。不要用 join/split,因为它会把元素转成字符串并丢失类型。深度特别大时,递归实现可能调用栈溢出,可以改成显式栈;生产中如果语义符合,优先使用原生 flat。
先定义 flatten(input, depth = Infinity) 返回一个新数组,不修改原数组。depth 表示最多展开几层,0 表示完全不展开,Infinity 表示尽可能展开所有嵌套数组。
遍历数组每个元素:如果元素仍是数组且 depth > 0,就递归展开 depth - 1 层;否则直接加入结果。递归返回的元素要按顺序追加,保证输出顺序和从左到右读取一致。
元素可能是数字、字符串、对象、null、undefined、Symbol 或函数。正确实现只判断 Array.isArray,不应把所有元素转成字符串,也不应通过 JSON 序列化处理。
原生 flat 会跳过被展开层级里的空槽。手写时如果用 for...of 遍历会读出 undefined,语义可能和原生不同;如果要贴近原生,可用索引遍历并判断 i in arr。
递归写法清晰,但极深嵌套会占用调用栈。面试可先写递归版本,再补充可以用显式栈保存待处理元素和剩余 depth 来避免调用栈溢出。
function flatten(input, depth = Infinity) {
const result = [];
function walk(arr, restDepth) {
for (let i = 0; i < arr.length; i += 1) {
if (!(i in arr)) continue;
const item = arr[i];
if (Array.isArray(item) && restDepth > 0) {
walk(item, restDepth - 1);
} else {
result.push(item);
}
}
}
walk(input, depth);
return result;
} 它会把元素变成字符串,null、undefined、对象、数组空位等语义都会变化,无法作为通用 flatten 实现。
每进入一层嵌套就减 1,depth 到 0 后不再展开当前数组,而是把它作为元素保留。默认可以设为 Infinity。
极深嵌套会导致调用栈溢出。可以用显式栈模拟递归,并保存元素和剩余可展开深度。
flat 只展开已有数组;flatMap 先对每个元素执行映射函数,再展开一层,等价于 map 后接一层 flat。