
本文介绍一种递归算法,用于将分层嵌套的数组(每层代表树的一个分支)展开为所有可能的字符串路径组合,适用于构建层级化枚举、笛卡尔积变体或树形结构遍历场景。
本文介绍一种递归算法,用于将分层嵌套的数组(每层代表树的一个分支)展开为所有可能的字符串路径组合,适用于构建层级化枚举、笛卡尔积变体或树形结构遍历场景。
该问题本质是按层级顺序进行受限笛卡尔积:不是简单地对所有子数组做全量交叉组合,而是要求第 i 层的每个子数组,仅与上一层中对应位置的元素组合——即存在隐式的“分支绑定关系”。例如,第二层的 [["c","d"], ["e","f","g"]] 中,第一个子数组 "c","d" 只作用于第一层 "a",第二个子数组 "e","f","g" 只作用于 "b";第三层的五个子数组则依次分配给前两层展开后的每个中间结果(共 5 个),形成精确的树状路径映射。
原始尝试的递归函数失败,原因在于它将后续层级视为统一集合进行全量拼接(即 item + subResult 对所有 subResult 遍历),忽略了层级间“子数组与父项一一对应”的拓扑约束。正确解法需维护一个偏移索引数组(offsets),动态跟踪当前递归深度下各层已处理到哪个子数组。
以下是经过优化、可读性强且健壮的实现:
function distributeTreeBranches(data) {
const combine = (index = 0, offsets = [0]) => {
// 确保 offsets 在下一深度有初始值(避免 undefined)
if (offsets.length = data.length) {
return [''];
}
// 当前层级数据:若为顶层(index === 0),直接取 data[0];
// 否则取 data[index][offsets[index]] —— 即绑定到上层结果对应位置的子数组
const currentItems = index === 0
? data[0]
: data[index][offsets[index]];
// 对 currentItems 中每一项,递归获取下一层所有后缀,并拼接
return currentItems.flatMap(item => {
const suffixes = combine(index + 1, offsets);
const result = suffixes.map(suffix => item + suffix);
// 关键:本层处理完一个子数组后,推进下一层的 offset
offsets[index + 1]++;
return result;
});
};
return combine();
}
// 测试用例
const input = [
["a", "b"],
[["c", "d"], ["e", "f", "g"]],
[["h", "i"], ["j"], ["k", "l"], ["m"], ["n", "o", "p"]]
];
console.log(distributeTreeBranches(input));
// 输出: ["ach", "aci", "adj", "bek", "bel", "bfm", "bgn", "bgo", "bgp"]
✅ 关键设计说明:
- offsets[i] 表示第 i 层当前正在处理的子数组索引;
- 每次进入新层级前,自动扩展 offsets 并初始化为 0;
- flatMap + map 实现自然的路径累积,避免手动 push;
- 终止条件返回 [''] 而非 [],确保字符串拼接始终有效(如 "x" + "" === "x")。
⚠️ 注意事项:
- 输入必须满足:data[0] 是一维字符串数组;data[i](i ≥ 1)是二维数组,其长度应等于前一层展开后的元素总数(否则会越界或遗漏);
- 该算法时间复杂度为 O(N),其中 N 为最终结果数组长度,空间复杂度主要由递归栈和 offsets 数组决定;
- 若需支持非字符串类型(如对象、数字),可将拼接逻辑 item + suffix 替换为自定义合并函数(如 Array.concat 或结构化合并)。
此方法清晰体现了树形结构的深度优先展开逻辑,是处理带约束路径枚举问题的典型范式。











