You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于递归函数合并嵌套无序列表的JavaScript实现问题

解决思路与代码实现

首先我得先明确你的需求:本质是要提取嵌套列表里所有叶子节点的完整路径(从最顶层父项到叶子的文本用空格拼接),对吧?看你给出的期望结果,确实只有没有子项的节点才会出现在最终数组里。

我给你两种实现方式,一种是不需要构建树的迭代式(更高效),另一种是你提到的递归式(基于树形结构),都能满足需求。

方式一:迭代式(无需构建树,直接遍历处理)

这种方式用一个栈来跟踪当前路径,遍历每一项时动态调整栈,同时判断当前项是否为叶子节点,是就拼接路径加入结果。

步骤拆解

  1. 预处理每一行:解析出每个项的层级(星号数量)和文本内容;
  2. 维护路径栈:每处理一个项,先把栈截断到当前层级的父级位置,再把当前文本加入栈;
  3. 判断叶子节点:如果下一项不存在,或者下一项的层级不大于当前层级,说明当前项没有子项,是叶子,就把栈内文本拼接后加入结果。

完整代码

function parseLine(line) {
  // 匹配开头的星号,兼容星号后有无空格的情况
  const match = line.match(/^(\*+)\s*(.*)$/);
  if (!match) return null; // 过滤无效行
  return {
    level: match[1].length,
    text: match[2].trim()
  };
}

function flattenNestedList(lines) {
  const parsedItems = lines.map(parseLine).filter(Boolean);
  const result = [];
  const pathStack = [];

  for (let i = 0; i < parsedItems.length; i++) {
    const current = parsedItems[i];
    const nextItem = parsedItems[i + 1];

    // 调整栈:截断到当前层级的前一层,确保栈长度等于当前层级-1
    pathStack.splice(current.level - 1);
    // 将当前文本加入栈,此时栈长度等于当前层级
    pathStack.push(current.text);

    // 判断是否为叶子节点:没有下一项,或者下一项层级不大于当前层级
    const isLeaf = !nextItem || nextItem.level <= current.level;
    if (isLeaf) {
      result.push(pathStack.join(' '));
    }
  }

  return result;
}

// 测试用例
const inputStr = `* item1
* item2
** item21
** item22
* item3
** item31
** item32
***item321
* item4`;
const lines = inputStr.split('\n');
console.log(flattenNestedList(lines));
// 输出:["item1", "item2 item21", "item2 item22", "item3 item31", "item3 item32 item321", "item4"]

方式二:递归式(先构建树形结构,再递归收集叶子路径)

如果你更倾向于递归实现,可以先把列表转换成树形结构,再递归遍历树的每个节点,收集叶子节点的完整路径。

步骤拆解

  1. 解析每一行:和上面一样,得到层级和文本;
  2. 构建树形结构:用栈来跟踪当前节点的层级,把每个项添加到对应的父节点下;
  3. 递归收集路径:遍历树的每个节点,如果是叶子节点(没有子节点),就把从根到该节点的路径拼接后加入结果。

完整代码

function parseLine(line) {
  const match = line.match(/^(\*+)\s*(.*)$/);
  if (!match) return null;
  return {
    level: match[1].length,
    text: match[2].trim()
  };
}

function buildTree(parsedItems) {
  const root = { children: [] };
  // 栈元素:{ node: 当前节点, level: 当前节点的层级 }
  const stack = [{ node: root, level: 0 }];

  for (const item of parsedItems) {
    // 找到父节点:弹出栈中层级 >= 当前项层级的节点,直到找到层级更小的父节点
    while (stack[stack.length - 1].level >= item.level) {
      stack.pop();
    }
    const parentNode = stack[stack.length - 1].node;
    const newNode = { text: item.text, children: [] };
    parentNode.children.push(newNode);
    stack.push({ node: newNode, level: item.level });
  }

  return root;
}

function collectLeafPaths(node, currentPath = [], result = []) {
  const updatedPath = [...currentPath, node.text];
  // 如果是叶子节点,添加路径到结果
  if (node.children.length === 0) {
    result.push(updatedPath.join(' '));
    return;
  }
  // 递归遍历子节点
  for (const child of node.children) {
    collectLeafPaths(child, updatedPath, result);
  }
}

// 测试用例
const inputStr = `* item1
* item2
** item21
** item22
* item3
** item31
** item32
***item321
* item4`;
const lines = inputStr.split('\n');
const parsedItems = lines.map(parseLine).filter(Boolean);
const tree = buildTree(parsedItems);
const result = [];
// 从根节点的子节点开始递归收集
tree.children.forEach(child => collectLeafPaths(child, [], result));
console.log(result);
// 输出和上面一致

两种方式都能完美得到你想要的结果,迭代式的性能更好一些,递归式的逻辑更直观,适合理解嵌套结构。

内容的提问来源于stack exchange,提问作者Pavel Sedlář

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.12 05:38:14