算法学习-树

183 阅读14分钟

最近在学习算法,之前一直是薄弱项(数组双重循环暴力排序水平)。

学习的过程中有不少的提升,主要就是对数据结构的理解又加深了一层,各种结构的优缺点,为什么会有这种结构,适用的场景是什么。

除此以外,深感写算法题就像在做脑筋急转弯,一道题自己写,太难了~ 再一看答案,感觉我又行了,过了两天再一看,我当时怎么写的来着?


写这篇文章,纯粹就当做一份笔记吧,方便后续复习回顾使用,快速的对一些做过的题目加深印象。

二叉树理解

基础数据结构

二叉树的基本结构都知道。一个节点分左右子节点,只有两个叉所以叫二叉树。

二叉树在数据结构设计方面既可以使用链表,也可以使用数组。

链表

image.png

数组

image.png

两种的区别,本质上还是数组和链表的区别。

数组要求空间连续,如果不是满二叉树,会有很多的空间浪费

链表则不要求空间连续,实现上会好很多。但是也缺少了下标随机访问的特性。

通常在LeetCode中解题使用的都是链表形式。

二叉树的遍历

二叉树的遍历是树相关算法中基础的基础,必须要记住。

通常我们遍历会有两种形式:

  • 一种先从上到下,再从左到右。(深度优先
  • 一种先从左到右,再从上到下。(广度优先

在看遍历之前先统一几个概念:

image.png

中间节点:树的节点。根节点,子节点我们都称为中间节点。

左/右子节点: 相对于中间节点来说,都有左/右子节点。

深度优先

基于中间节点的顺序,存在三种深度优先的遍历方式。

前序遍历:对于树中的任意节点来说,先打印中间节点,然后再打印它的左子节点,最后打印它的右子节点。(中间节点在前,所以为前序)

中序遍历:对于树中的任意节点来说,先打印它的左子节点,然后再打印中间节点,最后打印它的右子节点。(中间节点在中,所以为中序)

后续遍历:对于树中的任意节点来说,先打印它的左子节点,然后再打印它的右子节点,最后打印这个节点本身。(中间节点在后,所以为后序)


具体顺序如下图所示:

image.png

广度优先

广度优先遍历也叫层级遍历。就是按照从左至右,从上到下的顺序。

结合上图顺序就是:A -> B -> C -> D -> E -> F -> G

代码实现

深度优先

以前序遍历为例:先中间节点 -> 左子节点 -> 最后右子节点。从图中发现有两个特点:

  1. 从根节点开始,输出中间节点,然后输出左子节点,在输出左子节点的同时,也相当于输出了中间节点。也就是说从根节点开始,一直输出左子节点,直到叶子结点为止,我们就已经完成了中间节点、左子节点的输出。
  2. 左子节点只要存在,就先不找右子节点。不存在,则从最后一个左子节点开始,找右子节点输出。且右子节点作为中间节点情况时,也需要遵循先左后右顺序。

有些东西越解释越迷糊,先看代码吧!

// TreeNode的实现 在文字最后有
public void printTree(TreeNode node) {
    if (node == null) {
        return;
    }
    // 1. 前序 先输出当前节点
    System.out.println(node.val);
    // 2. 再输出 左子节点
    this.printTree(node.left);
    // 3. 再输出右子节点
    this.printTree(node.right);
}

代码看起来很简单。利用递归的思想,将复杂问题进行拆分,对于二叉树而言,我们没有必要看那么多层,只需要看两层即可。根节点 -> 左子节点 -> 右子节点

中序、后续也是一样,无非就是 1. 2. 3. 部分代码的位置顺序而已。

前序: 1 -> 2 -> 3

中序: 2 -> 1 -> 3

后续: 2 -> 3 -> 1


除了利用递归实现以外,我们还会通过迭代来去实现,代码如下:

public List<Integer> preorderTraversal(TreeNode root) {
    List<Integer> res = new ArrayList<>();
    if (root == null) {
        return res;
    }
    // 使用栈结构(先进后出)来记录节点
    Deque<TreeNode> queue = new LinkedList();
    while (root != null || !queue.isEmpty()) {
        while (root != null) {
            res.add(root.val);
            queue.offerFirst(root);
            root = root.left;
        }
        // 在左子节点为空时切换为右子节点,继续执行
        root = queue.remove();
        root = root.right;
    }
    return res;
}

这里可以思考一下,为什么有了递归还要用迭代,两者有什么区别呢?哪种更好呢?

常见算法题

二叉树

前序遍历(禁止使用递归)

前序的顺序: 中间节点 -》 左子节点 -》 右子节点

  1. 中间节点不为空,输出当前节点。(当前节点)
  2. 中间节点为左子节点。(左子节点)
  3. 中间节点为空,需要输出右侧节点。

重点:

  1. 使用队列记录当前节点,便于获取右侧节点。
  2. 记录当前节点的队列需要用栈结构,先进后出。
private static void qianxu1(TreeNode root) {
    List<String> res = new ArrayList<>();
    // 使用队列记录节点顺序,便于获取右侧子节点
    Deque<TreeNode> deque = new LinkedList();
    TreeNode node = root;
    // 当前节点可能为空 为空时 需要从队列中 获取新的节点
    while (!deque.isEmpty() || node != null) {
        while (node != null) {
            // 当前节点不为空 加入结果集
            res.add(node.getVal());
            // 记录当前节点,使用先进后出方式。从头部入队
            deque.offerFirst(node);
            node = node.getLeft();
        }
        // 使用先进后出方式。从头部弹出
        TreeNode right = deque.pop();
        node = right.getRight();
    }
    System.out.println(res);
}

层级遍历

按照层级逐层输出。

  1. 判断当前节点是否存在 子节点,存在则按照数据加入队列。先进先出。
  2. 每次从队列中弹出一个节点,重复步骤1,直到队列没有元素

重点:

如果需要感知第几层,则使用额外变量记录每一层子节点数量。由于每次循环从队列弹出节点,当一层节点弹出完毕后,重新获取队列中节点数量即为新的一层数量。

无需感知层级

private static void iteratorTree(TreeNode node) {
    List<String> result = new ArrayList<>(16);
    LinkedList<TreeNode> queue = new LinkedList<>();
    queue.add(node);
    while (queue.size() > 0) {
        TreeNode now = queue.remove();
        if (now.getLeft() != null) {
            queue.add(now.getLeft());
        }
        if (now.getRight() != null) {
            queue.add(now.getRight());
        }
        result.add(now.getVal());
    }
    System.out.println(result);
}

需要感知层级

private static void iteratorTree1(TreeNode node) {
    List<List<String>> result = new ArrayList<>(16);
    LinkedList<TreeNode> queue = new LinkedList<>();
    queue.add(node);
    while (queue.size() > 0) {
        int size = queue.size();
        List<String> item = new ArrayList<>();
        while (size > 0) {
            TreeNode now = queue.remove();
            if (now.getLeft() != null) {
                queue.add(now.getLeft());
            }
            if (now.getRight() != null) {
                queue.add(now.getRight());
            }
            item.add(now.getVal());
            size--;
        }
        result.add(item);
    }
    System.out.println(result);
}

锯齿遍历

题目

给你二叉树的根节点 root ,返回其节点值的 锯齿形层序遍历 。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。  

示例 1:

输入:root = [3,9,20,null,null,15,7] 输出:[[3],[20,9],[15,7]] 示例 2:

输入:root = [1] 输出:[[1]] 示例 3:

输入:root = [] 输出:[]  

提示:

树中节点数目在范围 [0, 2000] 内 -100 <= Node.val <= 100

思路:

  1. 按照层级遍历逻辑。区别就是使用交替方式添加左、右节点。不够好!!

锯齿实际是值的顺序,如果从节点顺序入手,虽然也可以实现,但是没有值击本质。

所以直接从值方面入手,使用值的头插或者尾插方式实现。

实现

private static void iteratorTree2(TreeNode node) {
    List<List<String>> result = new ArrayList<>();
    Queue<TreeNode> queue = new LinkedList();
    queue.add(node);
    int size = 1;
    boolean isLeft = true;
    Deque<String> levelVal = new LinkedList<>();
    while (queue.size() > 0) {
        TreeNode now = queue.remove();
        if (now.getLeft() != null) {
            queue.add(now.getLeft());
        }
        if (now.getRight() != null) {
            queue.add(now.getRight());
        }
        if (isLeft) {
            levelVal.offerLast(now.getVal());
        } else {
            levelVal.offerFirst(now.getVal());
        }
        size--;
        if (size == 0) {
            size = queue.size();
            isLeft = !isLeft;
            result.add(new ArrayList<>(levelVal));
            levelVal = new LinkedList<>();
        }
    }
    System.out.println(result);
}

最大深度

最大深度其实就是逐层遍历。可以按层级遍历,也可以直接递归找节点深度。

在逐层遍历的基础上,维护一个层数变量。

最小深度

题目

给定一个二叉树,找出其最小深度。

最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

说明: 叶子节点是指没有子节点的节点。

示例 1:

img

输入:root = [3,9,20,null,null,15,7]
输出:2

示例 2:

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

提示:

  • 树中节点数的范围在 [0, 105]
  • -1000 <= Node.val <= 1000

思路

最小深度有一个前提,如果根节点的左子节点为空,则使用右侧节点最小深度。如果都为空,则最小深度为1。

该题可以考虑使用递归实现。判断左右子节点哪个深度最小。

返回条件为:根节点为空,或者两个节点都为空。

  1. 当前节点(根节点)不为空
  2. 左右子节点不为空。
  3. 左子节点不为空,递归获取左子节点最小深度。
  4. 右子节点不为空,递归获取右子节点最小深度。
  5. 取最小值 + 1 返回。( +1 表示层级)

实现(待完善)

此法效率不高

public int minDepth(TreeNode root) {
    if (root == null) {
        return 0;
    }
    if (root.left == null && root.right == null) {
        return 1;
    }
    int minCount = Integer.MAX_VALUE;
    if (root.left != null) {
        minCount = Math.min(minDepth(root.left), minCount);
    }
    if (root.right != null) {
        minCount = Math.min(minDepth(root.right), minCount);
    }
    return minCount + 1;
}
​
// 解答成功:
// 执行耗时:9 ms,击败了34.19% 的Java用户
// 内存消耗:64.4 MB,击败了5.14% 的Java用户

路径总和

题目

给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false

叶子节点 是指没有子节点的节点。

示例 1:

img

输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出:true
解释:等于目标和的根节点到叶节点路径如上图所示。

示例 2:

img

输入:root = [1,2,3], targetSum = 5
输出:false
解释:树中存在两条根节点到叶子节点的路径:
(1 --> 2): 和为 3
(1 --> 3): 和为 4
不存在 sum = 5 的根节点到叶子节点的路径。

示例 3:

输入:root = [], targetSum = 0
输出:false
解释:由于树是空的,所以不存在根节点到叶子节点的路径。

提示:

  • 树中节点的数目在范围 [0, 5000]
  • -1000 <= Node.val <= 1000
  • -1000 <= targetSum <= 1000

思路

路径总和相当于找节点值。以题目实例1为例

  1. 首先验证根节点是否为空。不为空 总和 - 根节点值 =》 22 - 5
  2. 根节点左/右子节点不为空,继续去左右子节点检索,检索值为 17
  3. 如果左右子节点为空,判断 22 - 5 是否为0,为0也返回true。

实现

public boolean hasPathSum(TreeNode root, int targetSum) {
    if (root == null) {
        return false;
    }
    int sum = targetSum - root.val;
    if (root.left == null && root.right == null) {
        return sum == 0;
    }
    if (hasPathSum(root.left, sum) || hasPathSum(root.right, sum)) {
        return true;
    }
    return false;
}

两颗二叉树是否一致

是否一致的关键在于比对值是否一样。

可以采用深度优先(递归),也可以采用广度优先(迭代)。

思路

深度优先:

  1. 比对两个节点。

    1. 如果都为空,则一致
    2. 如果都不为空。比对值是否一样
    3. 如果一个为空,一个不为空则不一致
  2. 继续比对左侧节点

  3. 继续比对右侧节点

public static boolean validConsistent(TreeNode treeNode1, TreeNode treeNode2) {
    // 判断是否都为null
    if (treeNode1 == null && treeNode2 == null) {
        return true;
    }
    // 按断是否 一个 为null 一个不为 null
    if (treeNode1 == null || treeNode2 == null) {
        return false;
    }
    // 判断值是否一致
    if (!treeNode1.getVal().equals(treeNode2.getVal())) {
        return false;
    }
    // 判断子节点是否一致
    return validConsistent(treeNode1.getLeft(), treeNode2.getLeft()) && validConsistent(treeNode1.getRight(), treeNode2.getRight());
}

有序数组转二叉搜索树

题目

给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。

高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。

示例 1:

img

输入:nums = [-10,-3,0,5,9]
输出:[0,-3,9,-10,null,5]
解释:[0,-10,5,null,-3,null,9] 也将被视为正确答案:

示例 2:

img

输入:nums = [1,3]
输出:[3,1]
解释:[1,null,3][3,1] 都是高度平衡二叉搜索树。

提示:

  • 1 <= nums.length <= 104
  • -104 <= nums[i] <= 104
  • nums严格递增 顺序排列

思路

题目要求绝对平衡。而且提供的是有序的数组。所以思路为:取数组中间值作为节点值方式。

比如:

  1. 先取数组中间值为根节点值。
  2. 数组 0 ~ 中间值,范围内,再取中间值 作为左侧节点值。
  3. 数组 中间值 ~ 数组尾部 范围内,再取中间值 作为右侧节点值。
  4. 重复上述步骤,每次只取中间值作为节点值。

实现

public TreeNode sortedArrayToBST(int[] nums) {
    TreeNode root = new TreeNode();
    return arrayToBst(nums, 0, nums.length -1);
}

public TreeNode arrayToBst(int[] nums, int left, int right) {
    if (left > right) {
        return null;
    }
    int mid = left + (right - left) / 2;
    TreeNode leftNode = arrayToBst(nums, left, mid - 1);
    TreeNode rightNode = arrayToBst(nums, mid+1, right);
    return new TreeNode(nums[mid], leftNode, rightNode);
}

平衡二叉树

题目

给定一个二叉树,判断它是否是高度平衡的二叉树。

本题中,一棵高度平衡二叉树定义为:

一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。

示例 1:

img

输入:root = [3,9,20,null,null,15,7]
输出:true

示例 2:

img

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

示例 3:

输入:root = []
输出:true

提示:

  • 树中的节点数在范围 [0, 5000]
  • -104 <= Node.val <= 104

Related Topics

深度优先搜索

二叉树

思路

判断是否平衡,其实就是看左右子节点深度差是否大于1。

  1. 获取左右节点深度
  2. 比对深度差。

实现

错误实现;

public boolean isBalanced(TreeNode root) {
    if (root == null) {
        return true;
    }
    // 获取 左右 子节点最大深度 然后进行比对的方式 不完善
    int left = validBalanced(root.left);
    int right = validBalanced(root.right);
    return Math.abs(left - right) <= 1;
}
​
// 获取最大深度
public int validBalanced(TreeNode node) {
    if (node == null) {
        return 0;
    }
    int left = validBalanced(node.left);
    int right = validBalanced(node.right);
    return Integer.max(left, right) + 1;
}

正确实现:

public boolean isBalanced(TreeNode root) {
    if (root == null) {
        return true;
    }
    int left = validBalanced(root);
    return left >= 0;
}
​
public int validBalanced(TreeNode node) {
    if (node == null) {
        return 0;
    }
    int left = validBalanced(node.left);
    if (left < 0) {
        return left;
    }
    int right = validBalanced(node.right);
    if (right < 0) {
        return right;
    }
    // 这里需要注意 差值的绝对值 > 1
    if (Math.abs(left - right) > 1) {
        return -1;
    }
    // 这里需要注意返回最大值 如果范围最小值 那就都是 0
    return Integer.max(left, right) + 1;
}

左右节点对称

左侧节点的外侧节点与右侧节点的外侧节点一致。我们认为其对称。

此类问题可以考虑递归实现。为什么可以用递归?节点比对方式是一致的。

  1. 从根节点的左右节点开始比对。
  2. 先判断是否都不为空。
  3. 判断值是否一致。
  4. 然后递归比对当前节点的左侧节点是否一致。
  5. 然后递归比对当前节点的右侧节点是否一致。
private static boolean validSymmetry(TreeNode left, TreeNode right) {
    if (left == null && right == null) {
        return true;
    }
    if (left == null || right == null) {
        return false;
    }
    if (!left.getVal().equals(right.getVal())) {
        return false;
    }
    return validSymmetry(left.getLeft(), right.getRight()) && validSymmetry(left.getRight(), right.getLeft());
}

所有路径

返回二叉树从根节点到所有叶子节点路径。

使用递归方式,从根节点开始,依次将路径往下(左右子节点)传递,并拼装,直到子节点为空,将结果存储。

  1. 判断根节点不为空。如果节点为空,将路径加入结果集。
  2. 处理当前节点路径,
  3. 如果左子节点不为空,递归获取左子节点路径。
  4. 如果右子节点不为空,递归获取右子节点路径。
private static void printTreePath(TreeNode node, String path, List<String> result) {
    // 可以单独提取方法 处理根节点 以及 初始化结果集
    if (node == null) {
        return;
    }
    path = path + " -> " + node.val;
    if (node.left == null && node.right == null) {
        result.add(path);
        return;
    }
    if (node.left != null) {
        printTreePath(node.left, path, result);
    }
    if (node.right != null) {
        printTreePath(node.right, path, result);
    }
}

二叉树反转

左子节点变右子节点。

image.png

可以采用逐层遍历方式,自上而下翻转。

也可以采用前序遍历方式,自下而上翻转。

也可以采用递归方式,自左向右翻转。

// 使用逐层遍历方式
private static TreeNode reverseTree(TreeNode root) {
    Queue<TreeNode> queue = new LinkedList<>();
    queue.add(root);
    while (queue.size() > 0) {
        int size = queue.size();
        while (size > 0) {
            size--;
            TreeNode node = queue.remove();
            if (node.left != null) {
                queue.add(node.left);
            }
            if (node.right != null) {
                queue.add(node.right);
            }
            TreeNode temp = node.left;
            node.left = node.right;
            node.right = temp;
        }
    }
    return root;
}

// 使用前序遍历 方式
private static TreeNode reverseTree1(TreeNode root) {
    Deque<TreeNode> queue = new LinkedList();
    TreeNode node = root;
    while (queue.size() > 0 || node != null) {
        while (node != null) {
            queue.offerFirst(node);
            node = node.left;
        }
        TreeNode temp = queue.pop();
        node = temp.right;
        temp.right = temp.left;
        temp.left = node;
    }
    return root;
}
​
// 递归方式
private static TreeNode reverseTree2(TreeNode node) {
    if (node == null) {
        return null;
    }
    TreeNode temp = node.left;
    node.left = node.right;
    node.right = temp;
    reverseTree2(node.right);
    reverseTree2(node.left);
    return node;
}

最底层的最左侧值

image.png

该题本质还是层次遍历。

我们再层次遍历时会使用一个队列,这个队列按照层级入队,出队。而每次最后一个出队的就是最底层,最右侧节点。

相对于此题我们简单变换一下,让最左侧的节点最后出队即可。

因此入队的顺序需要调整为先右后左。

搜索树

搜索树最大的特点就是有顺序。左 -》 中 -》 右 (中序遍历)

查找节点

查找节点只需要遵循,如果当前节点值 < 查找值,去左子节点查询,否则去右子节点查询。如果最后节点为空表示不存在查找值。

验证搜索树

image.png

验证一棵树是否真正的有序。

该题其实就是按层次遍历树,在遍历的同时验证是否有序。开始以为只需要验证节点左中右顺序即可。但是实际上需要保证当前节点的左子节点值全部小于当前节点,右侧节点值全部大于当前节点。

image.png

比如上图,15的左子节点为8,小于 15 的父级节点 10 ,此树不为搜索树!!!

因此对于搜索树的判断,不能仅仅通过左中右顺序来判断,还需要考虑是否大于/小于父级节点。

这里需要记住一点,搜索树按照中序遍历是顺序递增的,满足此情况即为搜索树。


思路

搜索树是绝对有序的。这个等价于,搜索树使用中序遍历时也是绝对有序的。

所以验证是否为搜索树:

  1. 先取一个最小值作为上一个节点的值。
  2. 使用中序遍历,每次与上一个节点的值进行比对,必须要大于上个节点(如果小于等于上个节点则 不是搜索树)

错误示例1

private static TreeNode validSearchTree(TreeNode root) {
    Deque<TreeNode> queue = new LinkedList<>();
    // 这个赋值操作没有必要
    TreeNode node = root;
    // 这里使用 int的最小值不合适。 题目提示已经说明,树内值可能为 int 类型的最小值
    Integer lastVal = Integer.MIN_VALUE;
    while (queue.size() > 0 || node != null) {
        while (node != null) {
            queue.offerFirst(node);
            node = node.left;
        }
        node = queue.remove();
        // 这里没有考虑 等于的情况 子节点值 与 父节点值相等
        if (node.getIntVal() < lastVal) {
            return node;
        }
        lastVal = node.getIntVal();
        node = node.right;
    }
    return root;
}

本质还是边界值考虑问题: 边界值

  1. 如果只有一个节点
  2. 如果父子节点值相等
public boolean isValidBST(TreeNode root) {
    Deque<TreeNode> queue = new LinkedList<>();
    long lastVal = Long.MIN_VALUE;
    while (queue.size() > 0 || root != null) {
        while (root != null) {
            queue.offerFirst(root);
            root = root.left;
        }
        root = queue.remove();
        if (root.val <= lastVal) {
            return false;
        }
        lastVal = root.val;
        root = root.right;
    }
    return true;
}

错误示例2

public static void main(String[] args) { 
    TreeNode res = validSearchTree1(root, Integer.MIN_VALUE);
}
​
private static TreeNode validSearchTree1(TreeNode node, Integer pre) {
    if (node == null) {
        return node;
    }
    TreeNode errNode;
    if ((errNode = validSearchTree1(node.left, pre)) != null) {
        return errNode;
    }
    System.out.println(pre);
    if (node.getIntVal() <= pre) {
        System.out.println(false);
        return node;
    }
    pre = node.getIntVal();
    if ((errNode = validSearchTree1(node.right, pre)) != null) {
        return errNode;
    }
    return null;
}

错误位置:

  1. Integer 作为最小值。
  2. Integer参数传递属于引用传递。被调用者修改值不会对调用者产生影响。
class Solution {
    // 使用全局变量 并用 long 类型作为最小值
    long prev = Long.MIN_VALUE; 
    public boolean isValidBST(TreeNode root) {
        if (root == null) {
            return true;
        }
        if (!isValidBST(root.left)) {
            return false;
        }
         if (root.val <= prev) {
             return false;
         }
         prev = root.val;
         return isValidBST(root.right);
    }
}

补充

TreeNode结构定义


public class TreeNode {

    public int val;

    public TreeNode left;

    public TreeNode right;

    public TreeNode() {
    }

    public TreeNode(int val) {
        this.val = val;
    }

    public TreeNode(int val, TreeNode left, TreeNode right) {
        this.val = val;
        this.left = left;
        this.right = right;
    }

    /**
     *                           7
     *                 5                  10
     *           3           6       8           9
     *       1       2    4                            12
     *   0
     */
    public static TreeNode initNode() {
        TreeNode node0 = new TreeNode(0);
        TreeNode node1 = new TreeNode(1, node0, null);
        TreeNode node2 = new TreeNode(2);
        TreeNode node3 = new TreeNode(3, node1, node2);
        TreeNode node4 = new TreeNode(4);
        TreeNode node6 = new TreeNode(6, node4, null);
        TreeNode node5 = new TreeNode(5, node3, node6);
        TreeNode node12 = new TreeNode(12);
        TreeNode node8 = new TreeNode(8);
        TreeNode node9 = new TreeNode(9, null, node12);
        TreeNode node10 = new TreeNode(10, node8, node9);
        TreeNode root = new TreeNode(7, node5, node10);
        return root;
    }

    /**
     *                           7
     *                 5                  5
     *           3           6       6           3
     *       1                                         1
     *
     */
    public static TreeNode initSymmetryNode() {
        TreeNode node1l = new TreeNode(1);
        TreeNode node1r = new TreeNode(1);
        TreeNode node3l = new TreeNode(3, node1l, null);
        TreeNode node3r = new TreeNode(3, null, node1r);
        TreeNode node6l = new TreeNode(6);
        TreeNode node6r = new TreeNode(6);
        TreeNode node5l = new TreeNode(5, node3l, node6l);
        TreeNode node5r = new TreeNode(5, node6r, node3r);
        TreeNode root = new TreeNode(7, node5l, node5r);
        return root;
    }

    /**
     *                           20
     *                 10                  30
     *           5           15       25           35
     *       3       9    14                            40
     *   1
     */
    public static TreeNode initSearchNode() {
        TreeNode node1 = new TreeNode(1);
        TreeNode node3 = new TreeNode(3, node1, null);
        TreeNode node9 = new TreeNode(9);
        TreeNode node5 = new TreeNode(5, node3, node9);
        TreeNode node14 = new TreeNode(8);
        TreeNode node15 = new TreeNode(15, node14, null);
        TreeNode node10 = new TreeNode(10, node5, node15);
        TreeNode node40 = new TreeNode(40);
        TreeNode node35 = new TreeNode(35, null, node40);
        TreeNode node25 = new TreeNode(25);
        TreeNode node30 = new TreeNode(30, node25, node35);
        TreeNode root = new TreeNode(20, node10, node30);
        return root;
    }
}

迭代与递归比对

在二叉树遍历中,我们有两种方式,递归和迭代,对于两者效率,肯定是迭代要优于递归。因为递归属于方法级别的调用,在JVM中方法调用是需要在本地方法栈中进行记录的,而且如果出现深层递归,还会抛出StackOverFlowError。所以通常我们建议使用迭代代替递归。

当然,相对于递归来讲,迭代的实现通常要比递归麻烦很多,如果非深层次的处理,也是可以使用递归的。比如下方两种leetcode 提交记录。递归的内存消耗反而要高于迭代,原因想必也是迭代使用了额外的变量。

递归:执行时间 0ms, 内存 39.5 MB

image.png

迭代:

image.png