算法题 N 叉树的前序遍历

LeetCode 589. N 叉树的前序遍历

问题描述

给定一个 N 叉树的根节点 root,返回其节点值的 前序遍历

N 叉树在输入中按层序遍历进行序列化表示,每组子节点由 null 分隔(参见示例)。

示例

输入: root = [1,null,3,2,4,null,5,6]
输出: [1,3,5,6,2,4]

输入: 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]

算法思路

前序遍历

  1. 访问根节点
  2. 递归地前序遍历每个子树(从左到右)

方法

  1. 递归:最直观的实现方式
  2. 迭代:使用栈模拟递归过程
  3. Morris遍历:不适用N叉树(Morris遍历主要针对二叉树)

代码实现

方法一:递归

import java.util.*;

/*
// Definition for a Node.
class Node {
    public int val;
    public List<Node> children;

    public Node() {}

    public Node(int _val) {
        val = _val;
    }

    public Node(int _val, List<Node> _children) {
        val = _val;
        children = _children;
    }
};
*/

class Solution {
    /**
     * 递归:N叉树前序遍历
     * 
     * @param root N叉树根节点
     * @return 前序遍历结果列表
     */
    public List<Integer> preorder(Node root) {
        List<Integer> result = new ArrayList<>();
        preorderHelper(root, result);
        return result;
    }
    
    /**
     * 递归函数
     * 
     * @param node 当前节点
     * @param result 结果列表
     */
    private void preorderHelper(Node node, List<Integer> result) {
        // 空节点处理
        if (node == null) {
            return;
        }
        
        // 访问当前节点(根)
        result.add(node.val);
        
        // 递归遍历所有子节点(从左到右)
        if (node.children != null) {
            for (Node child : node.children) {
                preorderHelper(child, result);
            }
        }
    }
}

方法二:迭代

import java.util.*;

class Solution {
    /**
     * 迭代:使用栈实现N叉树前序遍历
     * 
     * @param root N叉树根节点
     * @return 前序遍历结果列表
     */
    public List<Integer> preorder(Node root) {
        List<Integer> result = new ArrayList<>();
        
        if (root == null) {
            return result;
        }
        
        Stack<Node> stack = new Stack<>();
        stack.push(root);
        
        while (!stack.isEmpty()) {
            // 弹出栈顶节点并访问
            Node node = stack.pop();
            result.add(node.val);
            
            // 将子节点逆序压入栈中(确保从左到右遍历)
            if (node.children != null) {
                // 从右到左压栈,这样弹出时就是从左到右
                for (int i = node.children.size() - 1; i >= 0; i--) {
                    stack.push(node.children.get(i));
                }
            }
        }
        
        return result;
    }
}

方法三:优化迭代

import java.util.*;

class Solution {
    /**
     * 优化迭代:使用Deque作为栈,代码更清晰
     * 
     * @param root N叉树根节点
     * @return 前序遍历结果列表
     */
    public List<Integer> preorder(Node root) {
        List<Integer> result = new ArrayList<>();
        
        if (root == null) {
            return result;
        }
        
        Deque<Node> stack = new ArrayDeque<>();
        stack.push(root);
        
        while (!stack.isEmpty()) {
            Node node = stack.pop();
            result.add(node.val);
            
            // 逆序添加子节点
            if (node.children != null) {
                Collections.reverse(node.children);
                for (Node child : node.children) {
                    stack.push(child);
                }
                // 恢复原顺序(如果需要保持原树不变)
                Collections.reverse(node.children);
            }
        }
        
        return result;
    }
}

方法四:使用LinkedList

import java.util.*;

class Solution {
    /**
     * 优化添加顺序:直接按正确顺序处理子节点
     * 
     * @param root N叉树根节点
     * @return 前序遍历结果列表
     */
    public List<Integer> preorder(Node root) {
        List<Integer> result = new ArrayList<>();
        
        if (root == null) {
            return result;
        }
        
        Stack<Node> stack = new Stack<>();
        stack.push(root);
        
        while (!stack.isEmpty()) {
            Node node = stack.pop();
            result.add(node.val);
            
            // 如果子节点存在,按逆序压栈
            if (node.children != null && !node.children.isEmpty()) {
                // 使用ListIterator从后往前遍历
                ListIterator<Node> iterator = node.children.listIterator(node.children.size());
                while (iterator.hasPrevious()) {
                    stack.push(iterator.previous());
                }
            }
        }
        
        return result;
    }
}

算法分析

  • 时间复杂度:O(n) - 每个节点访问一次
  • 空间复杂度
    • 递归:O(h) - h是树的高度(递归栈深度)
    • 迭代:O(h) - 栈空间(最坏情况下为O(n))

算法过程

输入:root = [1,null,3,2,4,null,5,6]

树结构

        1
     /  |  \
    3   2   4
   / \
  5   6

递归

preorder(1):
  result.add(1)
  preorder(3):
    result.add(3)
    preorder(5): result.add(5)
    preorder(6): result.add(6)
  preorder(2): result.add(2)
  preorder(4): result.add(4)

结果: [1,3,5,6,2,4]

迭代

初始: stack = [1]

步骤1: pop=1, result=[1], push=[4,2,3] → stack=[4,2,3]
步骤2: pop=3, result=[1,3], push=[6,5] → stack=[4,2,6,5]  
步骤3: pop=5, result=[1,3,5], push=[] → stack=[4,2,6]
步骤4: pop=6, result=[1,3,5,6], push=[] → stack=[4,2]
步骤5: pop=2, result=[1,3,5,6,2], push=[] → stack=[4]
步骤6: pop=4, result=[1,3,5,6,2,4], push=[] → stack=[]

结果: [1,3,5,6,2,4]

测试用例

public static void main(String[] args) {
    Solution solution = new Solution();
    
    // 构建N叉树
    Node buildTree(Integer[] arr) {
        if (arr.length == 0 || arr[0] == null) return null;
        
        Node root = new Node(arr[0]);
        Queue<Node> queue = new LinkedList<>();
        queue.offer(root);
        int i = 1;
        
        while (!queue.isEmpty() && i < arr.length) {
            Node node = queue.poll();
            List<Node> children = new ArrayList<>();
            
            // 收集当前节点的所有子节点(直到遇到null)
            while (i < arr.length && arr[i] != null) {
                Node child = new Node(arr[i]);
                children.add(child);
                queue.offer(child);
                i++;
            }
            i++; // 跳过null分隔符
            
            node.children = children;
        }
        
        return root;
    }
    
    // 打印列表
    void printList(List<Integer> list) {
        System.out.println(list);
    }
    
    // 测试用例1:标准示例
    Node root1 = buildTree(new Integer[]{1,null,3,2,4,null,5,6});
    List<Integer> result1 = solution.preorder(root1);
    System.out.print("Test 1: ");
    printList(result1); // [1,3,5,6,2,4]
    
    // 测试用例2:复杂树
    Node root2 = buildTree(new Integer[]{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});
    List<Integer> result2 = solution.preorder(root2);
    System.out.print("Test 2: ");
    printList(result2); // [1,2,3,6,7,11,14,4,8,12,5,9,13,10]
    
    // 测试用例3:单节点
    Node root3 = new Node(1);
    List<Integer> result3 = solution.preorder(root3);
    System.out.print("Test 3: ");
    printList(result3); // [1]
    
    // 测试用例4:空树
    List<Integer> result4 = solution.preorder(null);
    System.out.print("Test 4: ");
    printList(result4); // []
    
    // 测试用例5:只有根节点和子节点
    Node root5 = new Node(1, Arrays.asList(
        new Node(2), new Node(3), new Node(4)
    ));
    List<Integer> result5 = solution.preorder(root5);
    System.out.print("Test 5: ");
    printList(result5); // [1,2,3,4]
    
    // 测试用例6:深度为3的树
    Node root6 = new Node(1, Arrays.asList(
        new Node(2, Arrays.asList(new Node(3), new Node(4))),
        new Node(5, Arrays.asList(new Node(6)))
    ));
    List<Integer> result6 = solution.preorder(root6);
    System.out.print("Test 6: ");
    printList(result6); // [1,2,3,4,5,6]
    
    // 测试用例7:空子节点列表
    Node root7 = new Node(1);
    root7.children = new ArrayList<>(); // 空列表
    List<Integer> result7 = solution.preorder(root7);
    System.out.print("Test 7: ");
    printList(result7); // [1]
    
    // 测试用例8:null子节点
    Node root8 = new Node(1);
    root8.children = null;
    List<Integer> result8 = solution.preorder(root8);
    System.out.print("Test 8: ");
    printList(result8); // [1]
}

关键点

  1. 遍历顺序

    • 前序遍历:根 → 子节点(从左到右)
    • 必须按从左到右的顺序处理子节点
  2. 迭代的子节点压栈顺序

    • 逆序压栈(从右到左),保证弹出时是左到右
  3. 边界处理

    • 空树返回空列表
    • 叶子节点的children可能为null或空列表
  4. 空间复杂度

    • 平衡树:O(log n)
    • 线性树:O(n)

常见问题

  1. 为什么迭代要逆序压栈?

    • 栈是后进先出的数据结构
    • 如果按正常顺序压栈(左到右),弹出时会变成右到左
    • 逆序压栈确保访问顺序是从左到右
  2. 如何实现后序遍历?

    • 递归:先遍历所有子节点,再访问根节点
    • 迭代:可以使用两个栈,或者修改访问时机
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值