589. N 叉树的前序遍历(递归、迭代)

197 阅读3分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

不是因为看到了希望才坚持,而是因为坚持了才能看到希望。共勉

每日刷题第60天 2021.03.10

589. N 叉树的前序遍历

题目描述

  • 给定一个 叉树的根节点root ,返回 其节点值的 前序遍历 。
  • n叉树在输入中按层序遍历进行序列化表示,每组子节点由空值 null 分隔(请参见示例)。

示例

  • 示例1 image.png
输入: root = [1,null,3,2,4,null,5,6]
输出: [1,3,5,6,2,4]
  • 示例2 image.png
输入:root = [1,null,2,3,4,5,null,null,6,7,null,8,null,9,10,null,null,11,null,12,null,13,null,null,14]
输出:[1,2,3,6,7,11,14,4,8,12,5,9,13,10]

提示

  • 节点总数在范围 [0, 104]
  • 0 <= Node.val <= 104
  • n 叉树的高度小于或等于 1000

思路分析

递归解法(dfs)

  • 首先要会二叉树的前序遍历,那么多叉树的前序遍历就会了
    • 前序遍历:根、左节点、右节点
    • 中序遍历:左节点、根、右节点
    • 后序遍历:左节点、右节点、根
  • 对于一棵树来说,每个节点都相当于是根节点,其有属于自己的左子树和右子树,那么问题就可以简化为:重复求解 ==> 每个节点的打印 <=> 根节点(每个树)的打印
  • 对于递归模版的前中后序遍历,只需要改变根节点的打印顺序即可。
  • 模版代码
// 预处理
let ans = [];
if(root == null) return ans;
function traversal(node) {
  if (node == null) return;
  ans.push(node);    // 中
  traversal(node.left);  // 左
  traversal(node.right); // 右
}
traversal(root)

递归写法的步骤

  • 确定递归函数的参数和返回值(有时候可能是全局的,也有可能是局部的)
    • 确定哪些参数是递归中需要返回处理的,哪些参数是全局访问的。
  • 确定终止条件
    • 如果不写终止条件,会出现栈溢出的错误🙅,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈就会溢出。
  • 确定单层递归的逻辑
    • 将大问题转换成为多个重复的子问题,书写单层逻辑。

迭代解法(非递归解法)

  • 分析:递归的实现就是每一次递归调用会把相关的函数局部变量、参数值和返回地址等压入栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数。

拓展:二叉树的前序、中序、后序迭代法

  • 前序迭代:先访问的是根节点,需要先处理的也是根节点(较简单)
  • 中序迭代(稍难):先访问的是根节点,但是先处理的不再是根节点,而是左节点
  • 后序迭代:先访问的是根节点,但是先处理的也不是根节点,而是左节点
    • 先序遍历是中左右,后续遍历是左右中,只需要调整一下先序遍历的代码顺序,就变成中右左的遍历顺序,然后在反转result数组,输出的结果顺序就是左右中了。

AC代码

  • n叉树的前序遍历
/**
 * // Definition for a Node.
 * function Node(val, children) {
 *    this.val = val;
 *    this.children = children;
 * };
 */

/**
 * @param {Node|null} root
 * @return {number[]}
 */
var preorder = function(root) {
  // 模拟栈的写法
  // map 删除集合中的元素的方法map.delete()
  // 记录最终的结果的数组
  let ans = [];
  // 模拟栈数组
  let stack = [];
  // map集合记录当前有几个子节点已经被遍历过
  let nextIndex = new Map();
  let node = root;
  if(root == null) return ans;
  while(stack.length || node) {
    // 预判断:处理根节点为空的情况
    // console.log(node)
    // 循环查找所有的左边节点
    while(node){
      // 循环遍历到没有子节点
      ans.push(node.val);
      stack.push(node);
      if(node.children.length == 0){
        break;
      }
      nextIndex.set(node, 1);
      node = node.children[0];
    }
    // 下一个节点
    node = stack[stack.length - 1];
    // 查找下一个子节点
    const len = node.children.length;
    const i = nextIndex.get(node);
    if(i < len){
      // 还有子节点没有遍历
      // 子节点入栈
      nextIndex.set(node, i + 1);
      node = node.children[i];
    }else {
      stack.pop();
      nextIndex.delete(node);
      node = null;
    }
  }
  return ans;
};

总结

树🌲dfs通用模版

  • 树的中序、前序、后序迭代方法实现不太相同