【算法笔记】二叉树递归解题套路及其应用

二叉树的递归套路,是指利用递归解决二叉树类问题时的思路,其本质就是用递归的方式,向左右子树要信息,然后总结出整体的答案。本文整理了递归套路的解题思路和相关的应用题目。

1、二叉树的递归套路

二叉树的递归套路:

  • 可以解决面试中绝大多数的二叉树问题尤其是树型dp问题,本质是利用递归遍历二叉树的便利性。
  • 1)假设以X节点为头,假设可以向X左树和X右树要任何信息,
  • 2)在上一步的假设下,讨论以X为头节点的树,得到答案的可能性(最重要),
  • 3)列出所有可能性后,确定到底需要向左树和右树要什么样的信息,
  • 4)把左树信息和右树信息求全集,就是任何一棵子树都需要返回的信息S,
  • 5)递归函数都返回S,每一棵子树都这么要求,
  • 6)写代码,在代码中考虑如何把左树的信息和右树信息整合出整棵树的信息,
  • 利用二叉树的递归套路,最重要的是理清楚需要从左右子树要什么样的信息,然后根据左右子树的信息,当前节点需要怎样处理得出结论。

2、二叉树递归套路的应用

2.1、判断一棵树是否是满二叉树

判断一棵树是否是满二叉树:

满二叉树:满二叉树是一种特殊的二叉树,它的每个节点都有两个子节点,除了最后一层的叶子节点外,所有节点的度都是2。满二叉树的高度为h,节点数为2^h - 1。

  • 利用递归套路解法
java 复制代码
    /**
     * 利用二叉树的递归套路判断一棵树是否是满二叉树
     * 思路:
     * 递归判断左右子树是否是满二叉树,根据满二叉树的定义,
     * 如果一个节点是满二叉树,那么其左右子树也必须是满二叉树,而且左右子树的高度一样高。
     * 所以我们收集子树是否是满二叉树,收集子树的高度,就可以判断整棵树是否是满二叉树。
     * 左树满 && 右树满 && 左右树高度一样 -> 整棵树是满的
     */
    public static boolean isFullBinaryTree(Node head) {
        if (head == null) {
            return true;
        }
        // 调用递归函数,判断头节点是否是满二叉树
        return isFullBinaryTreeProcess(head).isFull;
    }

    /**
     * 判断一棵树是否是满二叉树的递归函数
     * 对于每一个节点,我们先拿到左右子树的信息,如果左右子树都是满二叉树,切左右子树的高度相同,则当前节点是满二叉树。
     * 当前节点的高度是左右高度的最大值+1
     */
    private static Info isFullBinaryTreeProcess(Node node) {
        // 如果节点为空,则可以认为是满二叉树,高度为0
        if (node == null) {
            return new Info(true, 0);
        }
        Info leftInfo = isFullBinaryTreeProcess(node.left);
        Info rightInfo = isFullBinaryTreeProcess(node.right);
        // 判断当前节点是否是满二叉树
        boolean isFull = leftInfo.isFull && rightInfo.isFull && leftInfo.height == rightInfo.height;
        // 当前节点的高度是左右高度的最大值+1
        int height = Math.max(leftInfo.height, rightInfo.height) + 1;
        return new Info(isFull, height);
    }

    /**
     * 利用递归套路判断一棵树是否是满二叉树的中间返回结果封装类
     */
    public static class Info {
        // 子树是否是满二叉树
        public boolean isFull;
        // 子树的高度
        public int height;

        public Info(boolean isFull, int height) {
            this.isFull = isFull;
            this.height = height;
        }
    }
  • 根据二叉树的高度和节点数判断一棵树是否是满二叉树实现的对数器
java 复制代码
    /**
     * 根据二叉树的高度和节点数判断一棵树是否是满二叉树实现的对数器
     * 思路:
     * 只有满二叉树满足 : 2 ^ h - 1 == n的条件,才是满二叉树。
     * 所以我们只需要手机整个数的最大高度和总的节点数,然后根据公式就可以计算出其是否是满二叉树。
     */
    public static boolean isFullBinaryTreeComparator(Node head) {
        if (head == null) {
            return true;
        }
        // 首先获取到二叉树的高度和所有节点
        TreeInfo treeInfo = getTreeInfo(head);
        // 只有满二叉树满足 : 2 ^ h - 1 == n的条件,才是满二叉树。
        return (1 << treeInfo.height) - 1 == treeInfo.allNodes;
    }

    /**
     * 递归获取二叉树的高度和节点信息,
     * 先获取左子树的高度和节点数,
     * 再获取右子树的高度和节点数,
     * 最后根据左右子树的高度和节点数,计算出当前节点的高度和节点数。
     */
    private static TreeInfo getTreeInfo(Node node) {
        if (node == null) {
            return new TreeInfo(0, 0);
        }
        TreeInfo leftInfo = getTreeInfo(node.left);
        TreeInfo rightInfo = getTreeInfo(node.right);
        int height = Math.max(leftInfo.height, rightInfo.height) + 1;
        int allNodes = leftInfo.allNodes + rightInfo.allNodes + 1;
        return new TreeInfo(height, allNodes);
    }


    /**
     * 二叉树的高度和节点数的封装类
     */
    public static class TreeInfo {
        private int height;
        private int allNodes;

        public TreeInfo(int height, int allNodes) {
            this.height = height;
            this.allNodes = allNodes;
        }
    }

整体代码和对数器的测试:

java 复制代码
/**
 * 判断一棵树是否是满二叉树
 * 满二叉树:
 * 满二叉树是一种特殊的二叉树,它的每个节点都有两个子节点,除了最后一层的叶子节点外,所有节点的度都是2。
 * 满二叉树的高度为h,节点数为2^h - 1。
 *
 */
public class IsFullBinaryTree {

    public static class Node {
        private int value;
        private Node left;
        private Node right;

        public Node(int value) {
            this.value = value;
        }
    }

    /**
     * 利用二叉树的递归套路判断一棵树是否是满二叉树
     * 思路:
     * 递归判断左右子树是否是满二叉树,根据满二叉树的定义,
     * 如果一个节点是满二叉树,那么其左右子树也必须是满二叉树,而且左右子树的高度一样高。
     * 所以我们收集子树是否是满二叉树,收集子树的高度,就可以判断整棵树是否是满二叉树。
     * 左树满 && 右树满 && 左右树高度一样 -> 整棵树是满的
     */
    public static boolean isFullBinaryTree(Node head) {
        if (head == null) {
            return true;
        }
        // 调用递归函数,判断头节点是否是满二叉树
        return isFullBinaryTreeProcess(head).isFull;
    }

    /**
     * 判断一棵树是否是满二叉树的递归函数
     * 对于每一个节点,我们先拿到左右子树的信息,如果左右子树都是满二叉树,切左右子树的高度相同,则当前节点是满二叉树。
     * 当前节点的高度是左右高度的最大值+1
     */
    private static Info isFullBinaryTreeProcess(Node node) {
        // 如果节点为空,则可以认为是满二叉树,高度为0
        if (node == null) {
            return new Info(true, 0);
        }
        Info leftInfo = isFullBinaryTreeProcess(node.left);
        Info rightInfo = isFullBinaryTreeProcess(node.right);
        // 判断当前节点是否是满二叉树
        boolean isFull = leftInfo.isFull && rightInfo.isFull && leftInfo.height == rightInfo.height;
        // 当前节点的高度是左右高度的最大值+1
        int height = Math.max(leftInfo.height, rightInfo.height) + 1;
        return new Info(isFull, height);
    }

    /**
     * 利用递归套路判断一棵树是否是满二叉树的中间返回结果封装类
     */
    public static class Info {
        // 子树是否是满二叉树
        public boolean isFull;
        // 子树的高度
        public int height;

        public Info(boolean isFull, int height) {
            this.isFull = isFull;
            this.height = height;
        }
    }

    /**
     * 根据二叉树的高度和节点数判断一棵树是否是满二叉树实现的对数器
     * 思路:
     * 只有满二叉树满足 : 2 ^ h - 1 == n的条件,才是满二叉树。
     * 所以我们只需要手机整个数的最大高度和总的节点数,然后根据公式就可以计算出其是否是满二叉树。
     */
    public static boolean isFullBinaryTreeComparator(Node head) {
        if (head == null) {
            return true;
        }
        // 首先获取到二叉树的高度和所有节点
        TreeInfo treeInfo = getTreeInfo(head);
        // 只有满二叉树满足 : 2 ^ h - 1 == n的条件,才是满二叉树。
        return (1 << treeInfo.height) - 1 == treeInfo.allNodes;
    }

    /**
     * 递归获取二叉树的高度和节点信息,
     * 先获取左子树的高度和节点数,
     * 再获取右子树的高度和节点数,
     * 最后根据左右子树的高度和节点数,计算出当前节点的高度和节点数。
     */
    private static TreeInfo getTreeInfo(Node node) {
        if (node == null) {
            return new TreeInfo(0, 0);
        }
        TreeInfo leftInfo = getTreeInfo(node.left);
        TreeInfo rightInfo = getTreeInfo(node.right);
        int height = Math.max(leftInfo.height, rightInfo.height) + 1;
        int allNodes = leftInfo.allNodes + rightInfo.allNodes + 1;
        return new TreeInfo(height, allNodes);
    }


    /**
     * 二叉树的高度和节点数的封装类
     */
    public static class TreeInfo {
        private int height;
        private int allNodes;

        public TreeInfo(int height, int allNodes) {
            this.height = height;
            this.allNodes = allNodes;
        }
    }


    public static void main(String[] args) {
        int maxLevel = 7;
        int maxValue = 100;
        int testTimes = 5000000;
        System.out.println("测试开始");
        boolean succeed = true;
        for (int i = 0; i < testTimes; i++) {
            Node head = generateRandomBinaryTree(maxLevel, maxValue);
            //horizontalPrintBinaryTree(head);
            boolean fullBinaryTree = isFullBinaryTree(head);
            boolean fullBinaryTreeComparator = isFullBinaryTreeComparator(head);
            if (fullBinaryTree != fullBinaryTreeComparator) {
                succeed = false;
                horizontalPrintBinaryTree(head);
                System.out.printf("fullBinaryTree: %s, fullBinaryTreeComparator: %s%n", fullBinaryTree, fullBinaryTreeComparator);
                break;
            }
        }
        System.out.println(succeed ? "successful!" : "error!");
    }

    // for test
    public static Node generateRandomBinaryTree(int maxLevel, int maxValue) {
        return generate(1, maxLevel, maxValue);
    }

    // for test
    public static Node generate(int level, int maxLevel, int maxValue) {
        if (level > maxLevel || Math.random() < 0.2) {
            return null;
        }
        Node head = new Node((int) (Math.random() * maxValue));
        head.left = generate(level + 1, maxLevel, maxValue);
        head.right = generate(level + 1, maxLevel, maxValue);
        return head;
    }

    public static void horizontalPrintBinaryTree(Node head) {
        System.out.println("============================Binary Tree:============================");
        if (head == null) {
            return;
        }
        // 中序遍历递归打印当前节点
        horizontalPrintInOrder(head, 0, "H", 17);
        System.out.println("====================================================================");
    }

    private static void horizontalPrintInOrder(Node head, int height, String to, int len) {
        if (head == null) {
            return;
        }
        // 先打印其右子树,这样可以确定到第一行,避免打印到最后打印不上去了
        horizontalPrintInOrder(head.right, height + 1, "v", len);
        // 打印当前节点
        String val = to + head.value + to;
        int lenM = val.length();
        int lenL = (len - lenM) / 2;
        int lenR = len - lenM - lenL;
        val = getSpace(lenL) + val + getSpace(lenR);
        System.out.println(getSpace(height * len) + val);
        // 打印其左子树
        horizontalPrintInOrder(head.left, height + 1, "^", len);
    }

    private static String getSpace(int num) {
        String space = " ";
        StringBuffer buf = new StringBuffer("");
        for (int i = 0; i < num; i++) {
            buf.append(space);
        }
        return buf.toString();
    }
}

2.2、判断一棵树是否是完全二叉树

判断一棵树是否是完全二叉树

  • 测试链接 : https://leetcode.cn/problems/check-completeness-of-a-binary-tree/
  • 完全二叉树:
  • 一棵深度为 k的二叉树,如果其前 k-1层所有节点数都达到最大值(即没有空缺),且第 k层的所有节点都连续集中在最左边,那么这棵树就是完全二叉树。
    1. 所有的叶子节点都出现在最后两层
    1. 最后一层的叶子节点都靠左对齐
    1. k-1层如果出现只有左子树的节点后,后面的节点必须都是叶子节点
  • 简单点说,完全二叉树就是所有叶子节点都在最后两层,而且如果一个点只有一孩子,那么这个孩子必须是左孩子。
      
    解法:
    利用二叉树的递归套路判断一棵树是否是完全二叉树
      
    思路:
  • 根据完全二叉树的定义,我们可以得到如下的推理:
  • 如果一个节点的左右子树都是满的,且高度一样,那这个节点就是满二叉树,也是完全二叉树。
  • 如果一个节点的左右子树都是满的,左树高度比右树高度高1,那这个节点就是完全二叉树。
  • 如果一个节点左树是满的,右数是完全二叉树,且左右子树高度相同,那这个节点就是完全二叉树。
  • 如果一个节点左树是完全二叉树,右树是满二叉树,左树高度比右树高度高1,那这个节点就是完全二叉树。
  • 以上就是完全二叉树的所有情况。从上面我们可以看出,从子节点要信息的话,要是否是满二叉树、是否是完全二叉树、高度这三个信息,
  • 根据左右子树的三个信息,就能判断出当前节点是否符合条件。
java 复制代码
    /**
     * 利用二叉树的递归套路判断一棵树是否是完全二叉树
     * 思路:
     * 根据完全二叉树的定义,我们可以得到如下的推理:
     * 如果一个节点的左右子树都是满的,且高度一样,那这个节点就是满二叉树,也是完全二叉树。
     * 如果一个节点的左右子树都是满的,左树高度比右树高度高1,那这个节点就是完全二叉树。
     * 如果一个节点左树是满的,右数是完全二叉树,且左右子树高度相同,那这个节点就是完全二叉树。
     * 如果一个节点左树是完全二叉树,右树是满二叉树,左树高度比右树高度高1,那这个节点就是完全二叉树。
     * 以上就是完全二叉树的所有情况。从上面我们可以看出,从子节点要信息的话,要是否是满二叉树、是否是完全二叉树、高度这三个信息,
     * 根据左右子树的三个信息,就能判断出当前节点是否符合条件。
     */
    public static boolean isCompleteBinaryTree(Node head) {
        if (head == null) {
            return true;
        }
        // 调用递归函数,判断是否是完全二叉树
        return completeBinaryTreeProcess(head).isComplete;
    }

    /**
     * 递归函数,判断一棵树是否是完全二叉树
     */
    private static Info completeBinaryTreeProcess(Node node) {
        if (node == null) {
            return new Info(true, true, 0);
        }
        // 递归调用左子树和右子树
        Info leftInfo = completeBinaryTreeProcess(node.left);
        Info rightInfo = completeBinaryTreeProcess(node.right);
        // 根据左右子树的信息,组装自己的信息
        // 高度是左右子树高度的最大值+1
        int height = Math.max(leftInfo.height, rightInfo.height) + 1;
        // 完全二叉树的条件是左右子树都是完全二叉树且高度相同
        boolean isFull = leftInfo.isFull && rightInfo.isFull && leftInfo.height == rightInfo.height;
        // 完全二叉树要根据左右子树的情况判断
        // 首先,如果已经判断出来当前的节点是满二叉树,那必然是完全二叉树
        if (isFull) {
            return new Info(true, true, height);
        }
        boolean isComplete = false;
        // 如果一个节点的左右子树都是满的,左树高度比右树高度高1,那这个节点就是完全二叉树。
        if (leftInfo.isFull && rightInfo.isFull && leftInfo.height == rightInfo.height + 1) {
            isComplete = true;
        }
        // 如果一个节点左树是满的,右数是完全二叉树,且左右子树高度相同,那这个节点就是完全二叉树。
        if (leftInfo.isFull && rightInfo.isComplete && leftInfo.height == rightInfo.height) {
            isComplete = true;
        }
        // 如果一个节点左树是完全二叉树,右树是满二叉树,左树高度比右树高度高1,那这个节点就是完全二叉树。
        if (leftInfo.isComplete && rightInfo.isFull && leftInfo.height == rightInfo.height + 1) {
            isComplete = true;
        }
        return new Info(false, isComplete, height);
    }

    /**
     * 递归套路的信息类
     */
    public static class Info {
        private boolean isFull;
        private boolean isComplete;
        private int height;

        public Info(boolean isFull, boolean isComplete, int height) {
            this.isFull = isFull;
            this.isComplete = isComplete;
            this.height = height;
        }

    }

利用层序遍历判断一棵树是否是完全二叉树的对数器

思路:

  • 完全二叉树的叶子节点必然是在最后两层,如果按层遍历整个二叉树,如果出现了一个只有左孩子的节点,就不能在出现没有孩子的节点了。
  • 根据这个特性,我们可以借助按层遍历的方式,判断一棵树是否是完全二叉树。
      
    过程:
    1. 利用层序遍历判断一棵树是否是完全二叉树
    1. 从左到右遍历二叉树,如果当前节点有右子树,但没有左子树,则一定不是完全二叉树
    1. 如果当前节点不是左右双全的节点,则后续节点必须都是叶子节点
java 复制代码
    /**
     * 利用层序遍历判断一棵树是否是完全二叉树的对数器
     * 思路:
     * 完全二叉树的叶子节点必然是在最后两层,如果按层遍历整个二叉树,如果出现了一个只有左孩子的节点,就不能在出现没有孩子的节点了。
     * 根据这个特性,我们可以借助按层遍历的方式,判断一棵树是否是完全二叉树。
     * 过程:
     * 1. 利用层序遍历判断一棵树是否是完全二叉树
     * 2. 从左到右遍历二叉树,如果当前节点有右子树,但没有左子树,则一定不是完全二叉树
     * 3. 如果当前节点不是左右双全的节点,则后续节点必须都是叶子节点
     */
    public static boolean isCompleteBinaryTreeComparator(Node head) {
        if (head == null) {
            return true;
        }
        // 按层遍历,先定一queue
        Queue<Node> queue = new LinkedList<>();
        queue.add(head);
        // 定义一个变量,记录是否已经出现过不是左右双全的节点
        boolean hasOnlyChild = false;
        while (!queue.isEmpty()) {
            Node node = queue.poll();
            // 拿到左右孩子
            Node left = node.left;
            Node right = node.right;
            // 如果有右孩子,但是没有左孩子,一定不是完全二叉树
            if (right != null && left == null) {
                return false;
            }
            // 如果已经出现过只有左孩子的节点,但是当前节点不是叶子节点那也一定不是完全二叉树
            if (hasOnlyChild && (left != null || right != null)) {
                return false;
            }
            // 如果出现了不是左右双全的节点,记录下来,因为是按层遍历,左侧的节点先遍历到,所以后续的节点必须都是叶子节点
            // 这里要注意,只要有一个没有就要改变状态,防止只出现右侧节点的情况
            if (left == null || right == null) {
                hasOnlyChild = true;
            }
            // 把左右孩子加入queue
            if (left != null) {
                queue.add(left);
            }
            if (right != null) {
                queue.add(right);
            }
        }
        // 如果遍历完了,都没有返回false,那就是完全二叉树
        return true;
    }

整体代码和测试如下:

java 复制代码
import java.util.LinkedList;
import java.util.Queue;

/**
 * 判断一棵树是否是完全二叉树
 * 测试链接 : https://leetcode.cn/problems/check-completeness-of-a-binary-tree/
 * 完全二叉树:
 * 一棵深度为 k的二叉树,如果其前 k-1层所有节点数都达到最大值(即没有空缺),且第 k层的所有节点都连续集中在最左边,那么这棵树就是完全二叉树。
 * 1. 所有的叶子节点都出现在最后两层
 * 2. 最后一层的叶子节点都靠左对齐
 * 3. k-1层如果出现只有左子树的节点后,后面的节点必须都是叶子节点
 * 简单点说,完全二叉树就是所有叶子节点都在最后两层,而且如果一个点只有一孩子,那么这个孩子必须是左孩子。
 */
public class IsCompleteBinaryTree {
    /**
     * 节点定义
     */
    public static class Node {
        public int value;
        public Node left;
        public Node right;

        public Node(int data) {
            this.value = data;
        }
    }

    /**
     * 利用二叉树的递归套路判断一棵树是否是完全二叉树
     * 思路:
     * 根据完全二叉树的定义,我们可以得到如下的推理:
     * 如果一个节点的左右子树都是满的,且高度一样,那这个节点就是满二叉树,也是完全二叉树。
     * 如果一个节点的左右子树都是满的,左树高度比右树高度高1,那这个节点就是完全二叉树。
     * 如果一个节点左树是满的,右数是完全二叉树,且左右子树高度相同,那这个节点就是完全二叉树。
     * 如果一个节点左树是完全二叉树,右树是满二叉树,左树高度比右树高度高1,那这个节点就是完全二叉树。
     * 以上就是完全二叉树的所有情况。从上面我们可以看出,从子节点要信息的话,要是否是满二叉树、是否是完全二叉树、高度这三个信息,
     * 根据左右子树的三个信息,就能判断出当前节点是否符合条件。
     */
    public static boolean isCompleteBinaryTree(Node head) {
        if (head == null) {
            return true;
        }
        // 调用递归函数,判断是否是完全二叉树
        return completeBinaryTreeProcess(head).isComplete;
    }

    /**
     * 递归函数,判断一棵树是否是完全二叉树
     */
    private static Info completeBinaryTreeProcess(Node node) {
        if (node == null) {
            return new Info(true, true, 0);
        }
        // 递归调用左子树和右子树
        Info leftInfo = completeBinaryTreeProcess(node.left);
        Info rightInfo = completeBinaryTreeProcess(node.right);
        // 根据左右子树的信息,组装自己的信息
        // 高度是左右子树高度的最大值+1
        int height = Math.max(leftInfo.height, rightInfo.height) + 1;
        // 完全二叉树的条件是左右子树都是完全二叉树且高度相同
        boolean isFull = leftInfo.isFull && rightInfo.isFull && leftInfo.height == rightInfo.height;
        // 完全二叉树要根据左右子树的情况判断
        // 首先,如果已经判断出来当前的节点是满二叉树,那必然是完全二叉树
        if (isFull) {
            return new Info(true, true, height);
        }
        boolean isComplete = false;
        // 如果一个节点的左右子树都是满的,左树高度比右树高度高1,那这个节点就是完全二叉树。
        if (leftInfo.isFull && rightInfo.isFull && leftInfo.height == rightInfo.height + 1) {
            isComplete = true;
        }
        // 如果一个节点左树是满的,右数是完全二叉树,且左右子树高度相同,那这个节点就是完全二叉树。
        if (leftInfo.isFull && rightInfo.isComplete && leftInfo.height == rightInfo.height) {
            isComplete = true;
        }
        // 如果一个节点左树是完全二叉树,右树是满二叉树,左树高度比右树高度高1,那这个节点就是完全二叉树。
        if (leftInfo.isComplete && rightInfo.isFull && leftInfo.height == rightInfo.height + 1) {
            isComplete = true;
        }
        return new Info(false, isComplete, height);
    }

    /**
     * 递归套路的信息类
     */
    public static class Info {
        private boolean isFull;
        private boolean isComplete;
        private int height;

        public Info(boolean isFull, boolean isComplete, int height) {
            this.isFull = isFull;
            this.isComplete = isComplete;
            this.height = height;
        }

    }

    /**
     * 利用层序遍历判断一棵树是否是完全二叉树的对数器
     * 思路:
     * 完全二叉树的叶子节点必然是在最后两层,如果按层遍历整个二叉树,如果出现了一个只有左孩子的节点,就不能在出现没有孩子的节点了。
     * 根据这个特性,我们可以借助按层遍历的方式,判断一棵树是否是完全二叉树。
     * 过程:
     * 1. 利用层序遍历判断一棵树是否是完全二叉树
     * 2. 从左到右遍历二叉树,如果当前节点有右子树,但没有左子树,则一定不是完全二叉树
     * 3. 如果当前节点不是左右双全的节点,则后续节点必须都是叶子节点
     */
    public static boolean isCompleteBinaryTreeComparator(Node head) {
        if (head == null) {
            return true;
        }
        // 按层遍历,先定一queue
        Queue<Node> queue = new LinkedList<>();
        queue.add(head);
        // 定义一个变量,记录是否已经出现过不是左右双全的节点
        boolean hasOnlyChild = false;
        while (!queue.isEmpty()) {
            Node node = queue.poll();
            // 拿到左右孩子
            Node left = node.left;
            Node right = node.right;
            // 如果有右孩子,但是没有左孩子,一定不是完全二叉树
            if (right != null && left == null) {
                return false;
            }
            // 如果已经出现过只有左孩子的节点,但是当前节点不是叶子节点那也一定不是完全二叉树
            if (hasOnlyChild && (left != null || right != null)) {
                return false;
            }
            // 如果出现了不是左右双全的节点,记录下来,因为是按层遍历,左侧的节点先遍历到,所以后续的节点必须都是叶子节点
            // 这里要注意,只要有一个没有就要改变状态,防止只出现右侧节点的情况
            if (left == null || right == null) {
                hasOnlyChild = true;
            }
            // 把左右孩子加入queue
            if (left != null) {
                queue.add(left);
            }
            if (right != null) {
                queue.add(right);
            }
        }
        // 如果遍历完了,都没有返回false,那就是完全二叉树
        return true;
    }

    public static void main(String[] args) {
        int maxLevel = 7;
        int maxValue = 100;
        int testTimes = 5000000;
        System.out.println("测试开始");
        boolean succeed = true;
        for (int i = 0; i < testTimes; i++) {
            Node head = generateRandomBinaryTree(maxLevel, maxValue);
            //horizontalPrintBinaryTree(head);
            boolean completeBinaryTree = isCompleteBinaryTree(head);
            boolean completeBinaryTreeComparator = isCompleteBinaryTreeComparator(head);
            if (completeBinaryTree != completeBinaryTreeComparator) {
                succeed = false;
                horizontalPrintBinaryTree(head);
                System.out.printf("completeBinaryTree: %s, completeBinaryTreeComparator: %s%n", completeBinaryTree, completeBinaryTreeComparator);
                break;
            }
        }
        System.out.println(succeed ? "successful!" : "error!");
    }

    // for test
    public static Node generateRandomBinaryTree(int maxLevel, int maxValue) {
        return generate(1, maxLevel, maxValue);
    }

    // for test
    public static Node generate(int level, int maxLevel, int maxValue) {
        if (level > maxLevel || Math.random() < 0.2) {
            return null;
        }
        Node head = new Node((int) (Math.random() * maxValue));
        head.left = generate(level + 1, maxLevel, maxValue);
        head.right = generate(level + 1, maxLevel, maxValue);
        return head;
    }

    public static void horizontalPrintBinaryTree(Node head) {
        System.out.println("============================Binary Tree:============================");
        if (head == null) {
            return;
        }
        // 中序遍历递归打印当前节点
        horizontalPrintInOrder(head, 0, "H", 17);
        System.out.println("====================================================================");
    }

    private static void horizontalPrintInOrder(Node head, int height, String to, int len) {
        if (head == null) {
            return;
        }
        // 先打印其右子树,这样可以确定到第一行,避免打印到最后打印不上去了
        horizontalPrintInOrder(head.right, height + 1, "v", len);
        // 打印当前节点
        String val = to + head.value + to;
        int lenM = val.length();
        int lenL = (len - lenM) / 2;
        int lenR = len - lenM - lenL;
        val = getSpace(lenL) + val + getSpace(lenR);
        System.out.println(getSpace(height * len) + val);
        // 打印其左子树
        horizontalPrintInOrder(head.left, height + 1, "^", len);
    }

    private static String getSpace(int num) {
        String space = " ";
        StringBuffer buf = new StringBuffer("");
        for (int i = 0; i < num; i++) {
            buf.append(space);
        }
        return buf.toString();
    }
}

2.3、判断一棵树是否是平衡二叉树

判断一棵树是否是平衡二叉树

利用二叉树的递归套路判断一棵树是否是平衡二叉树:

思路:

  • 对于每一个节点,递归判断其左右子树是否平衡,并判断左右子树的高度差是否超过1,如果超过1,则当前节点不平衡,返回false,否则返回true。
  • 所以,我们要递归向左右节点要两个信息,一个是左右节点是否平衡,另一个是左右节点为头的树的高度。
  • 这样,我们就可以判断出当前节点是否平衡,以及当前节点为头的树的高度。
java 复制代码
    /**
     * 利用二叉树的递归套路判断一棵树是否是平衡二叉树:
     * 思路:
     * 对于每一个节点,递归判断其左右子树是否平衡,并判断左右子树的高度差是否超过1,如果超过1,则当前节点不平衡,返回false,否则返回true。
     * 所以,我们要递归向左右节点要两个信息,一个是左右节点是否平衡,另一个是左右节点为头的树的高度。
     * 这样,我们就可以判断出当前节点是否平衡,以及当前节点为头的树的高度。
     */
    public static boolean isBalanceBinaryTree(Node head) {
        // 递归调用并返回当前节点是否是平衡节点
        return isBalanceBinaryTreeProcess(head).isBalance;
    }

    /**
     * 递归处理函数,返回当前节点的信息
     */
    private static Info isBalanceBinaryTreeProcess(Node node) {
        if (node == null) {
            return new Info(true, 0);
        }
        // 获取到左右子树的信息
        Info leftInfo = isBalanceBinaryTreeProcess(node.left);
        Info rightInfo = isBalanceBinaryTreeProcess(node.right);
        // 计算出当前节点的高度
        int height = Math.max(leftInfo.height, rightInfo.height) + 1;
        // 判断当前节点是否平衡:条件是左右子树都是平衡的,且高度差不大于1
        boolean isBalance = leftInfo.isBalance && rightInfo.isBalance && Math.abs(leftInfo.height - rightInfo.height) <= 1;
        return new Info(isBalance, height);
    }

    public static class Info {
        private boolean isBalance;
        private int height;

        public Info(boolean isBalance, int height) {
            this.isBalance = isBalance;
            this.height = height;
        }
    }

利用递归判断一棵树是否是平衡二叉树的方法2(对数器):

思路:

  • 在利用递归套路的方法一中,我们是递归向左右节点要两个信息,一个是左右节点是否平衡,另一个是左右节点为头的树的高度。
  • 在java中,函数有一个返回值,我们可以用来返回一个变量,可以用来返回某个节点的高度,那么就可以再定义一个全局的递归函数能修改到的值,来判断是否出现了不平衡的节点。
  • 所以,我们可以定义一个数组,因为数组是引用传递,我们可以用0位置来记录是否出现不平衡的点。
  • 如果出现了不平衡的点,那么我们就可以将数组的0位置设置为false。那么就可以递归判断出整棵树是否是平衡二叉树。
java 复制代码
    /**
     * 利用递归判断一棵树是否是平衡二叉树的方法2(对数器):
     * 思路:
     * 在利用递归套路的方法一中,我们是递归向左右节点要两个信息,一个是左右节点是否平衡,另一个是左右节点为头的树的高度。
     * 在java中,函数有一个返回值,我们可以用来返回一个变量,可以用来返回某个节点的高度,那么就可以再定义一个全局的递归函数能修改到的值,来判断是否出现了不平衡的节点。
     * 所以,我们可以定义一个数组,因为数组是引用传递,我们可以用0位置来记录是否出现不平衡的点。
     * 如果出现了不平衡的点,那么我们就可以将数组的0位置设置为false。那么就可以递归判断出整棵树是否是平衡二叉树。
     */
    public static boolean isBalanceBinaryTreeComparator(Node head) {
        // 定义一个只有一个元素的数组,用来记录是否出现了不平衡的点
        boolean[] ans = new boolean[1];
        ans[0] = true;
        // 递归调用判断是否是平衡二叉树
        comparatorProcess(head, ans);
        // 返回是否是平衡二叉树
        return ans[0];
    }

    /**
     * 递归处理函数,返回当前节点的高度,并判断是否出现了不平衡的点,将结果更新到ans数组中
     */
    private static int comparatorProcess(Node node, boolean[] ans) {
        if (node == null) {
            return 0;
        }
        // 递归调用左右子树
        int leftHeight = comparatorProcess(node.left, ans);
        int rightHeight = comparatorProcess(node.right, ans);
        // 判断当前节点是否平衡:条件是左右子树都是平衡的,且高度差不大于1
        if (Math.abs(leftHeight - rightHeight) > 1) {
            ans[0] = false;
        }
        // 返回当前节点的高度
        return Math.max(leftHeight, rightHeight) + 1;
    }

整体代码和测试如下:

java 复制代码
/**
 * 判断一棵树是否是平衡二叉树
 * 平衡二叉树:
 * 一棵二叉树的每个节点的左右子树的高度差的绝对值不超过1,那么这棵二叉树就是平衡二叉树。
 * 测试链接 : https://leetcode.cn/problems/balanced-binary-tree/
 */
public class IsBalanceBinaryTree {
    /**
     * 节点定义
     */
    public static class Node {
        public int value;
        public Node left;
        public Node right;

        public Node(int data) {
            this.value = data;
        }
    }

    /**
     * 利用二叉树的递归套路判断一棵树是否是平衡二叉树:
     * 思路:
     * 对于每一个节点,递归判断其左右子树是否平衡,并判断左右子树的高度差是否超过1,如果超过1,则当前节点不平衡,返回false,否则返回true。
     * 所以,我们要递归向左右节点要两个信息,一个是左右节点是否平衡,另一个是左右节点为头的树的高度。
     * 这样,我们就可以判断出当前节点是否平衡,以及当前节点为头的树的高度。
     */
    public static boolean isBalanceBinaryTree(Node head) {
        // 递归调用并返回当前节点是否是平衡节点
        return isBalanceBinaryTreeProcess(head).isBalance;
    }

    /**
     * 递归处理函数,返回当前节点的信息
     */
    private static Info isBalanceBinaryTreeProcess(Node node) {
        if (node == null) {
            return new Info(true, 0);
        }
        // 获取到左右子树的信息
        Info leftInfo = isBalanceBinaryTreeProcess(node.left);
        Info rightInfo = isBalanceBinaryTreeProcess(node.right);
        // 计算出当前节点的高度
        int height = Math.max(leftInfo.height, rightInfo.height) + 1;
        // 判断当前节点是否平衡:条件是左右子树都是平衡的,且高度差不大于1
        boolean isBalance = leftInfo.isBalance && rightInfo.isBalance && Math.abs(leftInfo.height - rightInfo.height) <= 1;
        return new Info(isBalance, height);
    }

    public static class Info {
        private boolean isBalance;
        private int height;

        public Info(boolean isBalance, int height) {
            this.isBalance = isBalance;
            this.height = height;
        }
    }

    /**
     * 利用递归判断一棵树是否是平衡二叉树的方法2(对数器):
     * 思路:
     * 在利用递归套路的方法一中,我们是递归向左右节点要两个信息,一个是左右节点是否平衡,另一个是左右节点为头的树的高度。
     * 在java中,函数有一个返回值,我们可以用来返回一个变量,可以用来返回某个节点的高度,那么就可以再定义一个全局的递归函数能修改到的值,来判断是否出现了不平衡的节点。
     * 所以,我们可以定义一个数组,因为数组是引用传递,我们可以用0位置来记录是否出现不平衡的点。
     * 如果出现了不平衡的点,那么我们就可以将数组的0位置设置为false。那么就可以递归判断出整棵树是否是平衡二叉树。
     */
    public static boolean isBalanceBinaryTreeComparator(Node head) {
        // 定义一个只有一个元素的数组,用来记录是否出现了不平衡的点
        boolean[] ans = new boolean[1];
        ans[0] = true;
        // 递归调用判断是否是平衡二叉树
        comparatorProcess(head, ans);
        // 返回是否是平衡二叉树
        return ans[0];
    }

    /**
     * 递归处理函数,返回当前节点的高度,并判断是否出现了不平衡的点,将结果更新到ans数组中
     */
    private static int comparatorProcess(Node node, boolean[] ans) {
        if (node == null) {
            return 0;
        }
        // 递归调用左右子树
        int leftHeight = comparatorProcess(node.left, ans);
        int rightHeight = comparatorProcess(node.right, ans);
        // 判断当前节点是否平衡:条件是左右子树都是平衡的,且高度差不大于1
        if (Math.abs(leftHeight - rightHeight) > 1) {
            ans[0] = false;
        }
        // 返回当前节点的高度
        return Math.max(leftHeight, rightHeight) + 1;
    }

    public static void main(String[] args) {
        int maxLevel = 7;
        int maxValue = 100;
        int testTimes = 5000000;
        System.out.println("测试开始");
        boolean succeed = true;
        for (int i = 0; i < testTimes; i++) {
            Node head = generateRandomBinaryTree(maxLevel, maxValue);
            //horizontalPrintBinaryTree(head);
            boolean balanceBinaryTree = isBalanceBinaryTree(head);
            boolean balanceBinaryTreeComparator = isBalanceBinaryTreeComparator(head);
            if (balanceBinaryTree != balanceBinaryTreeComparator) {
                succeed = false;
                horizontalPrintBinaryTree(head);
                System.out.printf("balanceBinaryTree: %s, balanceBinaryTreeComparator: %s%n", balanceBinaryTree, balanceBinaryTreeComparator);
                break;
            }
        }
        System.out.println(succeed ? "successful!" : "error!");
    }

    // for test
    public static Node generateRandomBinaryTree(int maxLevel, int maxValue) {
        return generate(1, maxLevel, maxValue);
    }

    // for test
    public static Node generate(int level, int maxLevel, int maxValue) {
        if (level > maxLevel || Math.random() < 0.2) {
            return null;
        }
        Node head = new Node((int) (Math.random() * maxValue));
        head.left = generate(level + 1, maxLevel, maxValue);
        head.right = generate(level + 1, maxLevel, maxValue);
        return head;
    }

    public static void horizontalPrintBinaryTree(Node head) {
        System.out.println("============================Binary Tree:============================");
        if (head == null) {
            return;
        }
        // 中序遍历递归打印当前节点
        horizontalPrintInOrder(head, 0, "H", 17);
        System.out.println("====================================================================");
    }

    private static void horizontalPrintInOrder(Node head, int height, String to, int len) {
        if (head == null) {
            return;
        }
        // 先打印其右子树,这样可以确定到第一行,避免打印到最后打印不上去了
        horizontalPrintInOrder(head.right, height + 1, "v", len);
        // 打印当前节点
        String val = to + head.value + to;
        int lenM = val.length();
        int lenL = (len - lenM) / 2;
        int lenR = len - lenM - lenL;
        val = getSpace(lenL) + val + getSpace(lenR);
        System.out.println(getSpace(height * len) + val);
        // 打印其左子树
        horizontalPrintInOrder(head.left, height + 1, "^", len);
    }

    private static String getSpace(int num) {
        String space = " ";
        StringBuffer buf = new StringBuffer("");
        for (int i = 0; i < num; i++) {
            buf.append(space);
        }
        return buf.toString();
    }

}

2.4、判断一棵树是否是搜索二叉树

判断一棵树是否是搜索二叉树

  • 测试链接 : https://leetcode.cn/problems/validate-binary-search-tree/
  • 搜索二叉树:
  • 一棵二叉树的每个节点的左子树的所有节点的值都小于该节点的值,右子树的所有节点的值都大于该节点的值,那么这棵二叉树就是搜索二叉树。
  • 经典搜索二叉树是没有重复值的,有重复值,也会在一个节点上放多个值,而不是将相同的值作为节点放到左子树或右子树。

利用二叉树的递归套路判断一棵树是否是搜索二叉树

思路:

  • 递归判断左右子树是否是搜索二叉树,
  • 获得左数的最大值和右数的最小值,如果当前节点的值大于左数的最大值,小于右数的最小值,则是搜索二叉树。
  • 所以某个节点需要向左右子树要三个信息,左右子树的最大值和最小值还有左右子树是否是搜索二叉树。
java 复制代码
    /**
     * 利用二叉树的递归套路判断一棵树是否是搜索二叉树
     * 思路:
     * 递归判断左右子树是否是搜索二叉树,
     * 获得左数的最大值和右数的最小值,如果当前节点的值大于左数的最大值,小于右数的最小值,则是搜索二叉树。
     * 所以某个节点需要向左右子树要三个信息,左右子树的最大值和最小值还有左右子树是否是搜索二叉树。
     */
    public static boolean isBinarySearchTree(Node head) {
        if (head == null) {
            return true;
        }
        return isBinarySearchTreeProcess(head).isBST;
    }

    /**
     * 递归判断返回的结果封装类
     */
    private static Info isBinarySearchTreeProcess(Node node) {
        // 递归结束条件,因为要返回左右子树的最大值和最小值,所以当节点为空时,无法确定最大值和最小值,所以返回null。
        // 将判断null的逻辑放到调用方去判断。
        if (node == null) {
            return null;
        }
        // 递归判断左右子树是否是搜索二叉树,获得左数的最大值和右数的最小值。
        Info leftInfo = isBinarySearchTreeProcess(node.left);
        Info rightInfo = isBinarySearchTreeProcess(node.right);
        // 计算当前节点为头的树的最大值和最小值。
        int max = node.value;
        int min = node.value;
        if (leftInfo != null) {
            max = Math.max(max, leftInfo.max);
            min = Math.min(min, leftInfo.min);
        }
        if (rightInfo != null) {
            max = Math.max(max, rightInfo.max);
            min = Math.min(min, rightInfo.min);
        }
        // 判断当前节点是否是搜索二叉树。
        boolean isBST = true;
        if (leftInfo != null && !leftInfo.isBST) {
            isBST = false;
        }
        if (rightInfo != null && !rightInfo.isBST) {
            isBST = false;
        }
        if (leftInfo != null && leftInfo.max >= node.value) {
            isBST = false;
        }
        if (rightInfo != null && rightInfo.min <= node.value) {
            isBST = false;
        }
        return new Info(isBST, max, min);
    }


    /**
     * 递归判断返回的结果封装类
     * 包含当前节点是否是搜索二叉树,当前节点为头的最大值和最小值
     */
    public static class Info {
        public boolean isBST;
        public int max;
        public int min;

        public Info(boolean isBST, int max, int min) {
            this.isBST = isBST;
            this.max = max;
            this.min = min;
        }

    }

暴力方法判断一颗树是否是搜索二叉树

思路:

  • 中序遍历这棵树,判断遍历结果是否是升序的。
  • 我们将中序遍历的结果放到一个数组中,判断数组是否是升序的。就可以判断出是否是搜索二叉树。
java 复制代码
    /**
     * 暴力方法判断一颗树是否是搜索二叉树
     * 思路:
     * 中序遍历这棵树,判断遍历结果是否是升序的。
     * 我们将中序遍历的结果放到一个数组中,判断数组是否是升序的。就可以判断出是否是搜索二叉树。
     */
    public static boolean isBinarySearchTreeComparator(Node head) {
        if (head == null) {
            return true;
        }
        // 定义一个ArrayList,用来存储中序遍历的结果。
        ArrayList<Integer> arr = new ArrayList<>();
        inOrderTraversal(head, arr);
        // 判断数组是否是升序的。
        for (int i = 0; i < arr.size() - 1; i++) {
            if (arr.get(i) >= arr.get(i + 1)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 中序遍历这棵树,将遍历结果放到一个数组中。
     */
    private static void inOrderTraversal(Node node, ArrayList<Integer> arr) {
        if (node == null) {
            return;
        }
        inOrderTraversal(node.left, arr);
        arr.add(node.value);
        inOrderTraversal(node.right, arr);
    }

整体代码和测试如下:

java 复制代码
import java.util.ArrayList;

/**
 * 判断一棵树是否是搜索二叉树
 * 测试链接 : https://leetcode.cn/problems/validate-binary-search-tree/
 * 搜索二叉树:
 * 一棵二叉树的每个节点的左子树的所有节点的值都小于该节点的值,右子树的所有节点的值都大于该节点的值,那么这棵二叉树就是搜索二叉树。
 * 经典搜索二叉树是没有重复值的,有重复值,也会在一个节点上放多个值,而不是将相同的值作为节点放到左子树或右子树。
 */
public class IsBinarySearchTree {
    /**
     * 节点定义
     */
    public static class Node {
        public int value;
        public Node left;
        public Node right;

        public Node(int data) {
            this.value = data;
        }
    }

    /**
     * 利用二叉树的递归套路判断一棵树是否是搜索二叉树
     * 思路:
     * 递归判断左右子树是否是搜索二叉树,
     * 获得左数的最大值和右数的最小值,如果当前节点的值大于左数的最大值,小于右数的最小值,则是搜索二叉树。
     * 所以某个节点需要向左右子树要三个信息,左右子树的最大值和最小值还有左右子树是否是搜索二叉树。
     */
    public static boolean isBinarySearchTree(Node head) {
        if (head == null) {
            return true;
        }
        return isBinarySearchTreeProcess(head).isBST;
    }

    /**
     * 递归判断返回的结果封装类
     */
    private static Info isBinarySearchTreeProcess(Node node) {
        // 递归结束条件,因为要返回左右子树的最大值和最小值,所以当节点为空时,无法确定最大值和最小值,所以返回null。
        // 将判断null的逻辑放到调用方去判断。
        if (node == null) {
            return null;
        }
        // 递归判断左右子树是否是搜索二叉树,获得左数的最大值和右数的最小值。
        Info leftInfo = isBinarySearchTreeProcess(node.left);
        Info rightInfo = isBinarySearchTreeProcess(node.right);
        // 计算当前节点为头的树的最大值和最小值。
        int max = node.value;
        int min = node.value;
        if (leftInfo != null) {
            max = Math.max(max, leftInfo.max);
            min = Math.min(min, leftInfo.min);
        }
        if (rightInfo != null) {
            max = Math.max(max, rightInfo.max);
            min = Math.min(min, rightInfo.min);
        }
        // 判断当前节点是否是搜索二叉树。
        boolean isBST = true;
        if (leftInfo != null && !leftInfo.isBST) {
            isBST = false;
        }
        if (rightInfo != null && !rightInfo.isBST) {
            isBST = false;
        }
        if (leftInfo != null && leftInfo.max >= node.value) {
            isBST = false;
        }
        if (rightInfo != null && rightInfo.min <= node.value) {
            isBST = false;
        }
        return new Info(isBST, max, min);
    }


    /**
     * 递归判断返回的结果封装类
     * 包含当前节点是否是搜索二叉树,当前节点为头的最大值和最小值
     */
    public static class Info {
        public boolean isBST;
        public int max;
        public int min;

        public Info(boolean isBST, int max, int min) {
            this.isBST = isBST;
            this.max = max;
            this.min = min;
        }

    }

    /**
     * 暴力方法判断一颗树是否是搜索二叉树
     * 思路:
     * 中序遍历这棵树,判断遍历结果是否是升序的。
     * 我们将中序遍历的结果放到一个数组中,判断数组是否是升序的。就可以判断出是否是搜索二叉树。
     */
    public static boolean isBinarySearchTreeComparator(Node head) {
        if (head == null) {
            return true;
        }
        // 定义一个ArrayList,用来存储中序遍历的结果。
        ArrayList<Integer> arr = new ArrayList<>();
        inOrderTraversal(head, arr);
        // 判断数组是否是升序的。
        for (int i = 0; i < arr.size() - 1; i++) {
            if (arr.get(i) >= arr.get(i + 1)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 中序遍历这棵树,将遍历结果放到一个数组中。
     */
    private static void inOrderTraversal(Node node, ArrayList<Integer> arr) {
        if (node == null) {
            return;
        }
        inOrderTraversal(node.left, arr);
        arr.add(node.value);
        inOrderTraversal(node.right, arr);
    }

    public static void main(String[] args) {
        int maxLevel = 7;
        int maxValue = 100;
        int testTimes = 5000000;
        System.out.println("测试开始");
        boolean succeed = true;
        for (int i = 0; i < testTimes; i++) {
            Node head = generateRandomBinaryTree(maxLevel, maxValue);
            //horizontalPrintBinaryTree(head);
            boolean isBST = isBinarySearchTree(head);
            boolean isBSTComparator = isBinarySearchTreeComparator(head);
            if (isBST != isBSTComparator) {
                succeed = false;
                horizontalPrintBinaryTree(head);
                System.out.printf("isBST: %s, isBSTComparator: %s%n", isBST, isBSTComparator);
                break;
            }
        }
        System.out.println(succeed ? "successful!" : "error!");
    }

    // for test
    public static Node generateRandomBinaryTree(int maxLevel, int maxValue) {
        return generate(1, maxLevel, maxValue);
    }

    // for test
    public static Node generate(int level, int maxLevel, int maxValue) {
        if (level > maxLevel || Math.random() < 0.2) {
            return null;
        }
        Node head = new Node((int) (Math.random() * maxValue));
        head.left = generate(level + 1, maxLevel, maxValue);
        head.right = generate(level + 1, maxLevel, maxValue);
        return head;
    }

    public static void horizontalPrintBinaryTree(Node head) {
        System.out.println("============================Binary Tree:============================");
        if (head == null) {
            return;
        }
        // 中序遍历递归打印当前节点
        horizontalPrintInOrder(head, 0, "H", 17);
        System.out.println("====================================================================");
    }

    private static void horizontalPrintInOrder(Node head, int height, String to, int len) {
        if (head == null) {
            return;
        }
        // 先打印其右子树,这样可以确定到第一行,避免打印到最后打印不上去了
        horizontalPrintInOrder(head.right, height + 1, "v", len);
        // 打印当前节点
        String val = to + head.value + to;
        int lenM = val.length();
        int lenL = (len - lenM) / 2;
        int lenR = len - lenM - lenL;
        val = getSpace(lenL) + val + getSpace(lenR);
        System.out.println(getSpace(height * len) + val);
        // 打印其左子树
        horizontalPrintInOrder(head.left, height + 1, "^", len);
    }

    private static String getSpace(int num) {
        String space = " ";
        StringBuffer buf = new StringBuffer("");
        for (int i = 0; i < num; i++) {
            buf.append(space);
        }
        return buf.toString();
    }
}

2.5、整棵二叉树的最大距离

求二叉树的最大距离: 给定一棵二叉树的头节点head,任何两个节点之间都存在距离,返回整棵二叉树的最大距离

递归套路求二叉树的最大距离;

思路:

  • 向左右子树分别要最大距离和高度,
  • 本节点的高度为左右子树的高度的最大值加1,
  • 本节点的最大距离为:
    1. 左子树的最大距离
    1. 右子树的最大距离
    1. 左子树的高度加右子树的高度加1
  • 三个中最大的一个
java 复制代码
    /**
     * 递归套路求二叉树的最大距离;
     * 思路:
     * 向左右子树分别要最大距离和高度,
     * 本节点的高度为左右子树的高度的最大值加1,
     * 本节点的最大距离为:
     * 1. 左子树的最大距离
     * 2. 右子树的最大距离
     * 3. 左子树的高度加右子树的高度加1
     * 三个中最大的一个
     */
    public static int maxDistance(Node head) {
        return maxDistanceProcess(head).maxDistance;
    }

    /**
     * 递归套路的处理过程
     */
    private static Info maxDistanceProcess(Node node) {
        if (node == null) {
            return new Info(0, 0);
        }
        // 递归获取左右子树的信息
        Info leftInfo = maxDistanceProcess(node.left);
        Info rightInfo = maxDistanceProcess(node.right);
        // 当前节点的高度
        int height = Math.max(leftInfo.height, rightInfo.height) + 1;
        // 更新当前节点的最大距离
        int maxDistance = Math.max(Math.max(leftInfo.maxDistance, rightInfo.maxDistance), leftInfo.height + rightInfo.height + 1);
        return new Info(maxDistance, height);
    }

    /**
     * 递归套路的信息类定义
     */
    public static class Info {
        public int maxDistance;
        public int height;

        public Info(int maxDistance, int height) {
            this.maxDistance = maxDistance;
            this.height = height;
        }
    }

暴力方法实现的对数器

思路:

  • 我们先记录每个节点和其父节点的对应关系,这样对于任意两个节点,我们都可以通过这种对应关系,找到其公共的头节点,从而计算出距离。
  • 然后我们将所有节点都放到一个list中,就可以通过遍历list,计算出任意两个节点之间的距离,然后取到最大的一个。
  • 时间复杂度为O(N^2),空间复杂度为O(N)
java 复制代码
    /**
     * 暴力方法实现的对数器
     * 思路:
     * 我们先记录每个节点和其父节点的对应关系,这样对于任意两个节点,我们都可以通过这种对应关系,找到其公共的头节点,从而计算出距离。
     * 然后我们将所有节点都放到一个list中,就可以通过遍历list,计算出任意两个节点之间的距离,然后取到最大的一个。
     * 时间复杂度为O(N^2),空间复杂度为O(N)
     */
    public static int maxDistanceComparator(Node head) {
        if (head == null) {
            return 0;
        }
        // 记录每个节点和其父节点的对应关系
        HashMap<Node, Node> parentMap = new HashMap<>();
        // 根节点的父节点为null
        parentMap.put(head, null);
        fillParentMap(head, parentMap);
        // 将所有节点都放到一个list中
        ArrayList<Node> nodeList = new ArrayList<>();
        fillNodeList(head, nodeList);
        // 遍历list,计算出任意两个节点之间的距离,然后取到最大的一个
        int maxDistance = 0;
        for (int i = 0; i < nodeList.size(); i++) {
            for (int j = i + 1; j < nodeList.size(); j++) {
                int distance = calculateDistance(parentMap, nodeList.get(i), nodeList.get(j));
                maxDistance = Math.max(maxDistance, distance);
            }
        }
        return maxDistance;


    }

    /**
     * 计算node1和node2之间的距离
     */
    private static int calculateDistance(HashMap<Node, Node> parentMap, Node node1, Node node2) {
        // 先将node1和node1的所有父节点都放到一个set中
        Set<Node> node1ParentSet = new HashSet<>();
        Node cur = node1;
        node1ParentSet.add(cur);
        while (cur != null) {
            node1ParentSet.add(cur);
            cur = parentMap.get(cur);
        }
        // node2往上查找,找到和node1公共的父节点
        cur = node2;
        while (!node1ParentSet.contains(cur)) {
            cur = parentMap.get(cur);
        }
        // 此时的cur就是node1和node2的公共父节点
        Node commonParent = cur;
        // 计算node1和node2的距离,就是node1到公共父节点的距离加上node2到公共父节点的距离
        int distance = 0;
        cur = node1;
        while (cur != commonParent) {
            distance++;
            cur = parentMap.get(cur);
        }
        cur = node2;
        while (cur != commonParent) {
            distance++;
            cur = parentMap.get(cur);
        }
        return distance;
    }

    /**
     * 填充parentMap,将所有节点和其父节点的对应关系都放到一个map中
     */
    private static void fillParentMap(Node node, HashMap<Node, Node> parentMap) {
        if (node == null) {
            return;
        }
        if (node.left != null) {
            parentMap.put(node.left, node);
            fillParentMap(node.left, parentMap);
        }
        if (node.right != null) {
            parentMap.put(node.right, node);
            fillParentMap(node.right, parentMap);
        }
    }

    /**
     * 填充nodeList,将所有节点按照前序遍历的顺序都放到一个list中
     */
    private static void fillNodeList(Node node, ArrayList<Node> nodeList) {
        if (node == null) {
            return;
        }
        nodeList.add(node);
        fillNodeList(node.left, nodeList);
        fillNodeList(node.right, nodeList);
    }

整体代码和测试如下:

java 复制代码
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

/**
 * 求二叉树的最大距离:
 * 给定一棵二叉树的头节点head,任何两个节点之间都存在距离,返回整棵二叉树的最大距离
 */
public class BinaryTreeMaxDistance {

    /**
     * 二叉树的节点定义
     */
    public static class Node {
        public int value;
        public Node left;
        public Node right;

        public Node(int data) {
            this.value = data;
        }
    }

    /**
     * 递归套路求二叉树的最大距离;
     * 思路:
     * 向左右子树分别要最大距离和高度,
     * 本节点的高度为左右子树的高度的最大值加1,
     * 本节点的最大距离为:
     * 1. 左子树的最大距离
     * 2. 右子树的最大距离
     * 3. 左子树的高度加右子树的高度加1
     * 三个中最大的一个
     */
    public static int maxDistance(Node head) {
        return maxDistanceProcess(head).maxDistance;
    }

    /**
     * 递归套路的处理过程
     */
    private static Info maxDistanceProcess(Node node) {
        if (node == null) {
            return new Info(0, 0);
        }
        // 递归获取左右子树的信息
        Info leftInfo = maxDistanceProcess(node.left);
        Info rightInfo = maxDistanceProcess(node.right);
        // 当前节点的高度
        int height = Math.max(leftInfo.height, rightInfo.height) + 1;
        // 更新当前节点的最大距离
        int maxDistance = Math.max(Math.max(leftInfo.maxDistance, rightInfo.maxDistance), leftInfo.height + rightInfo.height + 1);
        return new Info(maxDistance, height);
    }

    /**
     * 递归套路的信息类定义
     */
    public static class Info {
        public int maxDistance;
        public int height;

        public Info(int maxDistance, int height) {
            this.maxDistance = maxDistance;
            this.height = height;
        }
    }

    /**
     * 暴力方法实现的对数器
     * 思路:
     * 我们先记录每个节点和其父节点的对应关系,这样对于任意两个节点,我们都可以通过这种对应关系,找到其公共的头节点,从而计算出距离。
     * 然后我们将所有节点都放到一个list中,就可以通过遍历list,计算出任意两个节点之间的距离,然后取到最大的一个。
     * 时间复杂度为O(N^2),空间复杂度为O(N)
     */
    public static int maxDistanceComparator(Node head) {
        if (head == null) {
            return 0;
        }
        // 记录每个节点和其父节点的对应关系
        HashMap<Node, Node> parentMap = new HashMap<>();
        // 根节点的父节点为null
        parentMap.put(head, null);
        fillParentMap(head, parentMap);
        // 将所有节点都放到一个list中
        ArrayList<Node> nodeList = new ArrayList<>();
        fillNodeList(head, nodeList);
        // 遍历list,计算出任意两个节点之间的距离,然后取到最大的一个
        int maxDistance = 0;
        for (int i = 0; i < nodeList.size(); i++) {
            for (int j = i + 1; j < nodeList.size(); j++) {
                int distance = calculateDistance(parentMap, nodeList.get(i), nodeList.get(j));
                maxDistance = Math.max(maxDistance, distance);
            }
        }
        return maxDistance;


    }

    /**
     * 计算node1和node2之间的距离
     */
    private static int calculateDistance(HashMap<Node, Node> parentMap, Node node1, Node node2) {
        // 先将node1和node1的所有父节点都放到一个set中
        Set<Node> node1ParentSet = new HashSet<>();
        Node cur = node1;
        node1ParentSet.add(cur);
        while (cur != null) {
            node1ParentSet.add(cur);
            cur = parentMap.get(cur);
        }
        // node2往上查找,找到和node1公共的父节点
        cur = node2;
        while (!node1ParentSet.contains(cur)) {
            cur = parentMap.get(cur);
        }
        // 此时的cur就是node1和node2的公共父节点
        Node commonParent = cur;
        // 计算node1和node2的距离,就是node1到公共父节点的距离加上node2到公共父节点的距离
        int distance = 0;
        cur = node1;
        while (cur != commonParent) {
            distance++;
            cur = parentMap.get(cur);
        }
        cur = node2;
        while (cur != commonParent) {
            distance++;
            cur = parentMap.get(cur);
        }
        return distance;
    }

    /**
     * 填充parentMap,将所有节点和其父节点的对应关系都放到一个map中
     */
    private static void fillParentMap(Node node, HashMap<Node, Node> parentMap) {
        if (node == null) {
            return;
        }
        if (node.left != null) {
            parentMap.put(node.left, node);
            fillParentMap(node.left, parentMap);
        }
        if (node.right != null) {
            parentMap.put(node.right, node);
            fillParentMap(node.right, parentMap);
        }
    }

    /**
     * 填充nodeList,将所有节点按照前序遍历的顺序都放到一个list中
     */
    private static void fillNodeList(Node node, ArrayList<Node> nodeList) {
        if (node == null) {
            return;
        }
        nodeList.add(node);
        fillNodeList(node.left, nodeList);
        fillNodeList(node.right, nodeList);
    }


    public static void main(String[] args) {
        int maxLevel = 7;
        int maxValue = 100;
        int testTimes = 1000000;
        System.out.println("测试开始");
        boolean succeed = true;
        for (int i = 0; i < testTimes; i++) {
            Node head = generateRandomBinaryTree(maxLevel, maxValue);
            //horizontalPrintBinaryTree(head);
            int maxDistance = maxDistanceComparator(head);
            int maxDistanceComparator = maxDistanceComparator(head);
            if (maxDistance != maxDistanceComparator) {
                succeed = false;
                horizontalPrintBinaryTree(head);
                System.out.printf("maxDistance: %d, maxDistanceComparator: %d%n", maxDistance, maxDistanceComparator);
                break;
            }
        }
        System.out.println(succeed ? "successful!" : "error!");
    }

    // for test
    public static Node generateRandomBinaryTree(int maxLevel, int maxValue) {
        return generate(1, maxLevel, maxValue);
    }

    // for test
    public static Node generate(int level, int maxLevel, int maxValue) {
        if (level > maxLevel || Math.random() < 0.2) {
            return null;
        }
        Node head = new Node((int) (Math.random() * maxValue));
        head.left = generate(level + 1, maxLevel, maxValue);
        head.right = generate(level + 1, maxLevel, maxValue);
        return head;
    }

    public static void horizontalPrintBinaryTree(Node head) {
        System.out.println("============================Binary Tree:============================");
        if (head == null) {
            return;
        }
        // 中序遍历递归打印当前节点
        horizontalPrintInOrder(head, 0, "H", 17);
        System.out.println("====================================================================");
    }

    private static void horizontalPrintInOrder(Node head, int height, String to, int len) {
        if (head == null) {
            return;
        }
        // 先打印其右子树,这样可以确定到第一行,避免打印到最后打印不上去了
        horizontalPrintInOrder(head.right, height + 1, "v", len);
        // 打印当前节点
        String val = to + head.value + to;
        int lenM = val.length();
        int lenL = (len - lenM) / 2;
        int lenR = len - lenM - lenL;
        val = getSpace(lenL) + val + getSpace(lenR);
        System.out.println(getSpace(height * len) + val);
        // 打印其左子树
        horizontalPrintInOrder(head.left, height + 1, "^", len);
    }

    private static String getSpace(int num) {
        String space = " ";
        StringBuffer buf = new StringBuffer("");
        for (int i = 0; i < num; i++) {
            buf.append(space);
        }
        return buf.toString();
    }
}

2.6、求二叉树中最大搜索子树的节点数

求二叉树中最大搜索子树的节点数

  • 给定一棵二叉树的头节点head,返回这颗二叉树中最大的二叉搜索子树为头的节点数,
  • 整棵树有可能不是搜索二叉树,但是子树中有可能是,找出最大的那个搜索二叉树的节点数。
  • 在线测试链接 : https://leetcode.cn/problems/largest-bst-subtree

利用二叉树地柜套路求二叉树中最大搜索子树的节点数

思路:

  • 利用二叉树的递归套路先判断左右子树是否是搜索二叉树,然后判断加上当前节点是否是搜索二叉树,最后返回整个子树的结果。
  • 需要从左右子树要的信息:
  • 1.最大搜索二叉子树的大小
  • 2.最大值和最小值
  • 3.整个子树是否是搜索二叉树
  • 收集到左右子树的节点信息以后:
    1. 如果左右子树中有一个不是搜索二叉树,那么当前树也不是搜索二叉树。返回的最大搜索二叉树的节点就是两个中的较大的一个。
    1. 如果左右子树都是搜索二叉树,那么要判断加上当前节点是否是搜索二叉树,然后整体返回结果
java 复制代码
    /**
     * 二叉树节点
     */
    public static class TreeNode {
        public int val;
        public TreeNode left;
        public TreeNode right;

        public TreeNode(int value) {
            val = value;
        }
    }
    /**
     * 利用二叉树地柜套路求二叉树中最大搜索子树的节点数
     * 思路:
     * 利用二叉树的递归套路先判断左右子树是否是搜索二叉树,然后判断加上当前节点是否是搜索二叉树,最后返回整个子树的结果。
     * 需要从左右子树要的信息:
     * 1.最大搜索二叉子树的大小
     * 2.最大值和最小值
     * 3.整个子树是否是搜索二叉树
     * 收集到左右子树的节点信息以后:
     * 1. 如果左右子树中有一个不是搜索二叉树,那么当前树也不是搜索二叉树。返回的最大搜索二叉树的节点就是两个中的较大的一个。
     * 2. 如果左右子树都是搜索二叉树,那么要判断加上当前节点是否是搜索二叉树,然后整体返回结果
     */
    public static int largestBSTSubtree(TreeNode head) {
        if (head == null) {
            return 0;
        }
        return largestBSTSubtreeProcess(head).maxBSTSubtreeSize;
    }

    private static Info largestBSTSubtreeProcess(TreeNode node) {
        if (node == null) {
            return null;
        }
        // 递归求左右子树的信息
        Info leftInfo = largestBSTSubtreeProcess(node.left);
        Info rightInfo = largestBSTSubtreeProcess(node.right);
        // 计算当前节点的信息
        int maxBSTSubtreeSize = 0;
        int max = node.val;
        int min = node.val;
        if (leftInfo != null) {
            max = Math.max(max, leftInfo.max);
            min = Math.min(min, leftInfo.min);
            maxBSTSubtreeSize = Math.max(leftInfo.maxBSTSubtreeSize, maxBSTSubtreeSize);
        }
        if (rightInfo != null) {
            max = Math.max(max, rightInfo.max);
            min = Math.min(min, rightInfo.min);
            maxBSTSubtreeSize = Math.max(maxBSTSubtreeSize, rightInfo.maxBSTSubtreeSize);
        }
        // 判断当前节点是否是搜索二叉树
        boolean isBST = true;
        if (leftInfo != null && !leftInfo.isBST) {
            isBST = false;
        }
        if (rightInfo != null && !rightInfo.isBST) {
            isBST = false;
        }
        if (leftInfo != null && leftInfo.max >= node.val) {
            isBST = false;
        }
        if (rightInfo != null && rightInfo.min <= node.val) {
            isBST = false;
        }
        if (isBST) {
            maxBSTSubtreeSize = (leftInfo == null ? 0 : leftInfo.maxBSTSubtreeSize)
                    + (rightInfo == null ? 0 : rightInfo.maxBSTSubtreeSize) + 1;
        }
        return new Info(isBST, maxBSTSubtreeSize, max, min);
    }

    /**
     * 递归套路求二叉树中最大搜索子树的节点数
     */
    public static class Info {
        public boolean isBST;
        public int maxBSTSubtreeSize;
        public int max;
        public int min;

        public Info(boolean isBST, int maxBSTSubtreeSize, int max, int min) {
            this.isBST = isBST;
            this.maxBSTSubtreeSize = maxBSTSubtreeSize;
            this.max = max;
            this.min = min;
        }
    }

暴力方法实现的对数器

思路: 遍历每一个节点,然后判断以每个节点为头的子树是否是搜索二叉树,然后返回最大的一个。

java 复制代码
    /**
     * 暴力方法实现的对数器
     * 思路:
     * 遍历每一个节点,然后判断以每个节点为头的子树是否是搜索二叉树,然后返回最大的一个。
     */
    public static int largestBSTSubtreeComparator(TreeNode head) {
        if (head == null) {
            return 0;
        }
        // 如果以当前节点为头的子树是搜索二叉树,返回子树的节点数,
        int h = getBSTSize(head);
        if (h != 0) {
            return h;
        }
        // h为0,说明不是搜索二叉树,返回左右子树中最大的一个
        return Math.max(largestBSTSubtreeComparator(head.left), largestBSTSubtreeComparator(head.right));
    }

    /**
     * 如果以当前节点为头的子树是搜索二叉树,返回子树的节点数,否则返回0
     */
    private static int getBSTSize(TreeNode head) {
        if (head == null) {
            return 0;
        }
        // 中序遍历,将节点按值排序
        ArrayList<TreeNode> arr = new ArrayList<>();
        inOrderTraversal(head, arr);
        for (int i = 0; i < arr.size() - 1; i++) {
            if (arr.get(i).val >= arr.get(i + 1).val) {
                return 0;
            }
        }
        return arr.size();
    }

    private static void inOrderTraversal(TreeNode head, ArrayList<TreeNode> arr) {
        if (head == null) {
            return;
        }
        inOrderTraversal(head.left, arr);
        arr.add(head);
        inOrderTraversal(head.right, arr);
    }

整体代码和测试如下:

java 复制代码
import java.util.ArrayList;

/**
 * 求二叉树中最大搜索子树的节点数
 * 给定一棵二叉树的头节点head,返回这颗二叉树中最大的二叉搜索子树为头的节点数,
 * 整棵树有可能不是搜索二叉树,但是子树中有可能是,找出最大的那个搜索二叉树的节点数。
 * 在线测试链接 : https://leetcode.cn/problems/largest-bst-subtree
 */
public class MaxSubBinarySearchTreeSize {
    /**
     * 二叉树节点
     */
    public static class TreeNode {
        public int val;
        public TreeNode left;
        public TreeNode right;

        public TreeNode(int value) {
            val = value;
        }
    }

    /**
     * 利用二叉树地柜套路求二叉树中最大搜索子树的节点数
     * 思路:
     * 利用二叉树的递归套路先判断左右子树是否是搜索二叉树,然后判断加上当前节点是否是搜索二叉树,最后返回整个子树的结果。
     * 需要从左右子树要的信息:
     * 1.最大搜索二叉子树的大小
     * 2.最大值和最小值
     * 3.整个子树是否是搜索二叉树
     * 收集到左右子树的节点信息以后:
     * 1. 如果左右子树中有一个不是搜索二叉树,那么当前树也不是搜索二叉树。返回的最大搜索二叉树的节点就是两个中的较大的一个。
     * 2. 如果左右子树都是搜索二叉树,那么要判断加上当前节点是否是搜索二叉树,然后整体返回结果
     */
    public static int largestBSTSubtree(TreeNode head) {
        if (head == null) {
            return 0;
        }
        return largestBSTSubtreeProcess(head).maxBSTSubtreeSize;
    }

    private static Info largestBSTSubtreeProcess(TreeNode node) {
        if (node == null) {
            return null;
        }
        // 递归求左右子树的信息
        Info leftInfo = largestBSTSubtreeProcess(node.left);
        Info rightInfo = largestBSTSubtreeProcess(node.right);
        // 计算当前节点的信息
        int maxBSTSubtreeSize = 0;
        int max = node.val;
        int min = node.val;
        if (leftInfo != null) {
            max = Math.max(max, leftInfo.max);
            min = Math.min(min, leftInfo.min);
            maxBSTSubtreeSize = Math.max(leftInfo.maxBSTSubtreeSize, maxBSTSubtreeSize);
        }
        if (rightInfo != null) {
            max = Math.max(max, rightInfo.max);
            min = Math.min(min, rightInfo.min);
            maxBSTSubtreeSize = Math.max(maxBSTSubtreeSize, rightInfo.maxBSTSubtreeSize);
        }
        // 判断当前节点是否是搜索二叉树
        boolean isBST = true;
        if (leftInfo != null && !leftInfo.isBST) {
            isBST = false;
        }
        if (rightInfo != null && !rightInfo.isBST) {
            isBST = false;
        }
        if (leftInfo != null && leftInfo.max >= node.val) {
            isBST = false;
        }
        if (rightInfo != null && rightInfo.min <= node.val) {
            isBST = false;
        }
        if (isBST) {
            maxBSTSubtreeSize = (leftInfo == null ? 0 : leftInfo.maxBSTSubtreeSize)
                    + (rightInfo == null ? 0 : rightInfo.maxBSTSubtreeSize) + 1;
        }
        return new Info(isBST, maxBSTSubtreeSize, max, min);
    }

    /**
     * 递归套路求二叉树中最大搜索子树的节点数
     */
    public static class Info {
        public boolean isBST;
        public int maxBSTSubtreeSize;
        public int max;
        public int min;

        public Info(boolean isBST, int maxBSTSubtreeSize, int max, int min) {
            this.isBST = isBST;
            this.maxBSTSubtreeSize = maxBSTSubtreeSize;
            this.max = max;
            this.min = min;
        }
    }

    /**
     * 暴力方法实现的对数器
     * 思路:
     * 遍历每一个节点,然后判断以每个节点为头的子树是否是搜索二叉树,然后返回最大的一个。
     */
    public static int largestBSTSubtreeComparator(TreeNode head) {
        if (head == null) {
            return 0;
        }
        // 如果以当前节点为头的子树是搜索二叉树,返回子树的节点数,
        int h = getBSTSize(head);
        if (h != 0) {
            return h;
        }
        // h为0,说明不是搜索二叉树,返回左右子树中最大的一个
        return Math.max(largestBSTSubtreeComparator(head.left), largestBSTSubtreeComparator(head.right));
    }

    /**
     * 如果以当前节点为头的子树是搜索二叉树,返回子树的节点数,否则返回0
     */
    private static int getBSTSize(TreeNode head) {
        if (head == null) {
            return 0;
        }
        // 中序遍历,将节点按值排序
        ArrayList<TreeNode> arr = new ArrayList<>();
        inOrderTraversal(head, arr);
        for (int i = 0; i < arr.size() - 1; i++) {
            if (arr.get(i).val >= arr.get(i + 1).val) {
                return 0;
            }
        }
        return arr.size();
    }

    private static void inOrderTraversal(TreeNode head, ArrayList<TreeNode> arr) {
        if (head == null) {
            return;
        }
        inOrderTraversal(head.left, arr);
        arr.add(head);
        inOrderTraversal(head.right, arr);
    }

    public static void main(String[] args) {
        int maxLevel = 7;
        int maxValue = 100;
        int testTimes = 5000000;
        System.out.println("测试开始");
        boolean succeed = true;
        for (int i = 0; i < testTimes; i++) {
            TreeNode head = generateRandomBinaryTree(maxLevel, maxValue);
            //horizontalPrintBinaryTree(head);
            int largestBSTSubtree = largestBSTSubtree(head);
            int largestBSTSubtreeComparator = largestBSTSubtreeComparator(head);
            if (largestBSTSubtree != largestBSTSubtreeComparator) {
                succeed = false;
                horizontalPrintBinaryTree(head);
                System.out.printf("largestBSTSubtree: %d, largestBSTSubtreeComparator: %d%n", largestBSTSubtree, largestBSTSubtreeComparator);
                break;
            }
        }
        System.out.println(succeed ? "successful!" : "error!");
    }

    // for test
    public static TreeNode generateRandomBinaryTree(int maxLevel, int maxValue) {
        return generate(1, maxLevel, maxValue);
    }

    // for test
    public static TreeNode generate(int level, int maxLevel, int maxValue) {
        if (level > maxLevel || Math.random() < 0.2) {
            return null;
        }
        TreeNode head = new TreeNode((int) (Math.random() * maxValue));
        head.left = generate(level + 1, maxLevel, maxValue);
        head.right = generate(level + 1, maxLevel, maxValue);
        return head;
    }

    public static void horizontalPrintBinaryTree(TreeNode head) {
        System.out.println("============================Binary Tree:============================");
        if (head == null) {
            return;
        }
        // 中序遍历递归打印当前节点
        horizontalPrintInOrder(head, 0, "H", 17);
        System.out.println("====================================================================");
    }

    private static void horizontalPrintInOrder(TreeNode head, int height, String to, int len) {
        if (head == null) {
            return;
        }
        // 先打印其右子树,这样可以确定到第一行,避免打印到最后打印不上去了
        horizontalPrintInOrder(head.right, height + 1, "v", len);
        // 打印当前节点
        String val = to + head.val + to;
        int lenM = val.length();
        int lenL = (len - lenM) / 2;
        int lenR = len - lenM - lenL;
        val = getSpace(lenL) + val + getSpace(lenR);
        System.out.println(getSpace(height * len) + val);
        // 打印其左子树
        horizontalPrintInOrder(head.left, height + 1, "^", len);
    }

    private static String getSpace(int num) {
        String space = " ";
        StringBuffer buf = new StringBuffer("");
        for (int i = 0; i < num; i++) {
            buf.append(space);
        }
        return buf.toString();
    }
}

2.7、求二叉树中最大搜索子树的头结点

求二叉树中最大的搜索二叉子树的头节点

  • 整棵树有可能不是搜索二叉树,但是子树中有可能是,找出最大的那个搜索二叉树的头节点
    利用二叉树地柜套路求二叉树中最大的搜索二叉子树的头节点
      思路:
  • 利用二叉树的递归套路先判断左右子树是否是搜索二叉树,然后判断加上当前节点是否是搜索二叉树,最后返回整个子树的结果。
  • 需要从左右子树要的信息:
  • 1.最大搜索二叉子树的头结点
  • 2.最大值和最小值
    1. 左右子树的最大搜索二叉子树的节点数
  • 收集到左右子树的节点信息以后:
    1. 如果左右子树中有一个不是搜索二叉树,那么当前树也不是搜索二叉树。返回的最大搜索二叉树的节点就是两个中的较大的一个。
    1. 如果左右子树都是搜索二叉树,那么要判断加上当前节点是否是搜索二叉树,然后整体返回结果
java 复制代码
    /**
     * 利用二叉树地柜套路求二叉树中最大的搜索二叉子树的头节点
     * 思路:
     * 利用二叉树的递归套路先判断左右子树是否是搜索二叉树,然后判断加上当前节点是否是搜索二叉树,最后返回整个子树的结果。
     * 需要从左右子树要的信息:
     * 1.最大搜索二叉子树的头结点
     * 2.最大值和最小值
     * 3. 左右子树的最大搜索二叉子树的节点数
     * 收集到左右子树的节点信息以后:
     * 1. 如果左右子树中有一个不是搜索二叉树,那么当前树也不是搜索二叉树。返回的最大搜索二叉树的节点就是两个中的较大的一个。
     * 2. 如果左右子树都是搜索二叉树,那么要判断加上当前节点是否是搜索二叉树,然后整体返回结果
     */
    public static TreeNode largestBSTSubtreeHead(TreeNode head) {
        if (head == null) {
            return null;
        }
        return largestBSTSubtreeHeadProcess(head).maxBSTSubtreeHead;
    }

    private static Info largestBSTSubtreeHeadProcess(TreeNode node) {
        if (node == null) {
            return null;
        }
        // 获取到左右子树的信息
        Info leftInfo = largestBSTSubtreeHeadProcess(node.left);
        Info rightInfo = largestBSTSubtreeHeadProcess(node.right);
        // 计算出当前节点的信息
        int max = node.val;
        int min = node.val;
        if (leftInfo != null) {
            max = Math.max(max, leftInfo.max);
            min = Math.min(min, leftInfo.min);
        }
        if (rightInfo != null) {
            max = Math.max(max, rightInfo.max);
            min = Math.min(min, rightInfo.min);
        }
        TreeNode maxBSTSubtreeHead = null;
        int maxSubBSTSize = 0;
        // 判断左子树是否是搜索二叉树
        boolean isLeftBST = leftInfo == null || (leftInfo.maxBSTSubtreeHead == node.left && leftInfo.max < node.val);
        // 判断右子树是否是搜索二叉树
        boolean isRightBST = rightInfo == null || (rightInfo.maxBSTSubtreeHead == node.right && rightInfo.min > node.val);
        // 如果左右子树都是搜索二叉树,那么要判断加上当前节点是否是搜索二叉树
        if (isLeftBST && isRightBST) {
            maxBSTSubtreeHead = node;
            maxSubBSTSize = (leftInfo == null ? 0 : leftInfo.maxSubBSTSize) + (rightInfo == null ? 0 : rightInfo.maxSubBSTSize) + 1;
            return new Info(maxBSTSubtreeHead, maxSubBSTSize, max, min);
        }
        // 到这里,已经排除了当前节点能作为搜索二叉树的情况,将左右节点中较大的一个作为最大搜索二叉树的头节点
        maxSubBSTSize = leftInfo == null ? 0 : leftInfo.maxSubBSTSize;
        maxBSTSubtreeHead = leftInfo == null ? null : leftInfo.maxBSTSubtreeHead;
        if (rightInfo != null && rightInfo.maxSubBSTSize > maxSubBSTSize) {
            maxSubBSTSize = rightInfo.maxSubBSTSize;
            maxBSTSubtreeHead = rightInfo.maxBSTSubtreeHead;
        }
        return new Info(maxBSTSubtreeHead, maxSubBSTSize, max, min);
    }

    /**
     * 递归套路求二叉树中最大搜索子树的节点
     */
    public static class Info {
        public TreeNode maxBSTSubtreeHead;
        public int maxSubBSTSize;
        public int max;
        public int min;

        public Info(TreeNode maxBSTSubtreeHead, int maxSubBSTSize, int max, int min) {
            this.maxBSTSubtreeHead = maxBSTSubtreeHead;
            this.maxSubBSTSize = maxSubBSTSize;
            this.max = max;
            this.min = min;
        }
    }

暴力方法实现的对数器

思路:遍历每一个节点,然后判断以每个节点为头的子树是否是搜索二叉树,然后返回最大的一个。

java 复制代码
    /**
     * 暴力方法实现的对数器
     * 思路:
     * 遍历每一个节点,然后判断以每个节点为头的子树是否是搜索二叉树,然后返回最大的一个。
     */
    public static TreeNode largestBSTSubtreeHeadComparator(TreeNode head) {
        if (head == null) {
            return null;
        }
        // 如果以当前节点为头的子树是搜索二叉树,返回子树的节点数,
        int h = getBSTSize(head);
        if (h != 0) {
            return head;
        }
        // h为0,说明不是搜索二叉树,返回左右子树中最大的一个
        TreeNode leftAns = largestBSTSubtreeHeadComparator(head.left);
        TreeNode rightAns = largestBSTSubtreeHeadComparator(head.right);
        return getBSTSize(leftAns) >= getBSTSize(rightAns) ? leftAns : rightAns;
    }

    /**
     * 如果以当前节点为头的子树是搜索二叉树,返回子树的节点数,否则返回0
     */
    private static int getBSTSize(TreeNode head) {
        if (head == null) {
            return 0;
        }
        // 中序遍历,将节点按值排序
        ArrayList<TreeNode> arr = new ArrayList<>();
        inOrderTraversal(head, arr);
        for (int i = 0; i < arr.size() - 1; i++) {
            if (arr.get(i).val >= arr.get(i + 1).val) {
                return 0;
            }
        }
        return arr.size();
    }

    private static void inOrderTraversal(TreeNode head, ArrayList<TreeNode> arr) {
        if (head == null) {
            return;
        }
        inOrderTraversal(head.left, arr);
        arr.add(head);
        inOrderTraversal(head.right, arr);
    }

整体代码和测试如下:

java 复制代码
import java.util.ArrayList;

/**
 * 求二叉树中最大的搜索二叉子树的头节点
 * 整棵树有可能不是搜索二叉树,但是子树中有可能是,找出最大的那个搜索二叉树的头节点
 */
public class MaxSubBinarySearchTreeHead {


    /**
     * 二叉树节点
     */
    public static class TreeNode {
        public int val;
        public TreeNode left;
        public TreeNode right;

        public TreeNode(int value) {
            val = value;
        }
    }

    /**
     * 利用二叉树地柜套路求二叉树中最大的搜索二叉子树的头节点
     * 思路:
     * 利用二叉树的递归套路先判断左右子树是否是搜索二叉树,然后判断加上当前节点是否是搜索二叉树,最后返回整个子树的结果。
     * 需要从左右子树要的信息:
     * 1.最大搜索二叉子树的头结点
     * 2.最大值和最小值
     * 3. 左右子树的最大搜索二叉子树的节点数
     * 收集到左右子树的节点信息以后:
     * 1. 如果左右子树中有一个不是搜索二叉树,那么当前树也不是搜索二叉树。返回的最大搜索二叉树的节点就是两个中的较大的一个。
     * 2. 如果左右子树都是搜索二叉树,那么要判断加上当前节点是否是搜索二叉树,然后整体返回结果
     */
    public static TreeNode largestBSTSubtreeHead(TreeNode head) {
        if (head == null) {
            return null;
        }
        return largestBSTSubtreeHeadProcess(head).maxBSTSubtreeHead;
    }

    private static Info largestBSTSubtreeHeadProcess(TreeNode node) {
        if (node == null) {
            return null;
        }
        // 获取到左右子树的信息
        Info leftInfo = largestBSTSubtreeHeadProcess(node.left);
        Info rightInfo = largestBSTSubtreeHeadProcess(node.right);
        // 计算出当前节点的信息
        int max = node.val;
        int min = node.val;
        if (leftInfo != null) {
            max = Math.max(max, leftInfo.max);
            min = Math.min(min, leftInfo.min);
        }
        if (rightInfo != null) {
            max = Math.max(max, rightInfo.max);
            min = Math.min(min, rightInfo.min);
        }
        TreeNode maxBSTSubtreeHead = null;
        int maxSubBSTSize = 0;
        // 判断左子树是否是搜索二叉树
        boolean isLeftBST = leftInfo == null || (leftInfo.maxBSTSubtreeHead == node.left && leftInfo.max < node.val);
        // 判断右子树是否是搜索二叉树
        boolean isRightBST = rightInfo == null || (rightInfo.maxBSTSubtreeHead == node.right && rightInfo.min > node.val);
        // 如果左右子树都是搜索二叉树,那么要判断加上当前节点是否是搜索二叉树
        if (isLeftBST && isRightBST) {
            maxBSTSubtreeHead = node;
            maxSubBSTSize = (leftInfo == null ? 0 : leftInfo.maxSubBSTSize) + (rightInfo == null ? 0 : rightInfo.maxSubBSTSize) + 1;
            return new Info(maxBSTSubtreeHead, maxSubBSTSize, max, min);
        }
        // 到这里,已经排除了当前节点能作为搜索二叉树的情况,将左右节点中较大的一个作为最大搜索二叉树的头节点
        maxSubBSTSize = leftInfo == null ? 0 : leftInfo.maxSubBSTSize;
        maxBSTSubtreeHead = leftInfo == null ? null : leftInfo.maxBSTSubtreeHead;
        if (rightInfo != null && rightInfo.maxSubBSTSize > maxSubBSTSize) {
            maxSubBSTSize = rightInfo.maxSubBSTSize;
            maxBSTSubtreeHead = rightInfo.maxBSTSubtreeHead;
        }
        return new Info(maxBSTSubtreeHead, maxSubBSTSize, max, min);
    }

    /**
     * 递归套路求二叉树中最大搜索子树的节点
     */
    public static class Info {
        public TreeNode maxBSTSubtreeHead;
        public int maxSubBSTSize;
        public int max;
        public int min;

        public Info(TreeNode maxBSTSubtreeHead, int maxSubBSTSize, int max, int min) {
            this.maxBSTSubtreeHead = maxBSTSubtreeHead;
            this.maxSubBSTSize = maxSubBSTSize;
            this.max = max;
            this.min = min;
        }
    }

    /**
     * 暴力方法实现的对数器
     * 思路:
     * 遍历每一个节点,然后判断以每个节点为头的子树是否是搜索二叉树,然后返回最大的一个。
     */
    public static TreeNode largestBSTSubtreeHeadComparator(TreeNode head) {
        if (head == null) {
            return null;
        }
        // 如果以当前节点为头的子树是搜索二叉树,返回子树的节点数,
        int h = getBSTSize(head);
        if (h != 0) {
            return head;
        }
        // h为0,说明不是搜索二叉树,返回左右子树中最大的一个
        TreeNode leftAns = largestBSTSubtreeHeadComparator(head.left);
        TreeNode rightAns = largestBSTSubtreeHeadComparator(head.right);
        return getBSTSize(leftAns) >= getBSTSize(rightAns) ? leftAns : rightAns;
    }

    /**
     * 如果以当前节点为头的子树是搜索二叉树,返回子树的节点数,否则返回0
     */
    private static int getBSTSize(TreeNode head) {
        if (head == null) {
            return 0;
        }
        // 中序遍历,将节点按值排序
        ArrayList<TreeNode> arr = new ArrayList<>();
        inOrderTraversal(head, arr);
        for (int i = 0; i < arr.size() - 1; i++) {
            if (arr.get(i).val >= arr.get(i + 1).val) {
                return 0;
            }
        }
        return arr.size();
    }

    private static void inOrderTraversal(TreeNode head, ArrayList<TreeNode> arr) {
        if (head == null) {
            return;
        }
        inOrderTraversal(head.left, arr);
        arr.add(head);
        inOrderTraversal(head.right, arr);
    }

    public static void main(String[] args) {
        int maxLevel = 7;
        int maxValue = 100;
        int testTimes = 5000000;
        System.out.println("测试开始");
        boolean succeed = true;
        for (int i = 0; i < testTimes; i++) {
            TreeNode head = generateRandomBinaryTree(maxLevel, maxValue);
            //horizontalPrintBinaryTree(head);
            TreeNode largestBSTSubtree = largestBSTSubtreeHead(head);
            TreeNode largestBSTSubtreeComparator = largestBSTSubtreeHeadComparator(head);
            if (largestBSTSubtree != largestBSTSubtreeComparator) {
                succeed = false;
                horizontalPrintBinaryTree(head);
                String largestBSTSubtreeVal = largestBSTSubtree != null ? String.valueOf(largestBSTSubtree.val) : "null";
                String largestBSTSubtreeComparatorVal = largestBSTSubtreeComparator != null ? String.valueOf(largestBSTSubtreeComparator.val) : "null";
                System.out.printf("largestBSTSubtree: %s, largestBSTSubtreeComparator: %s%n", largestBSTSubtreeVal, largestBSTSubtreeComparatorVal);
                break;
            }
        }
        System.out.println(succeed ? "successful!" : "error!");
    }

    // for test
    public static TreeNode generateRandomBinaryTree(int maxLevel, int maxValue) {
        return generate(1, maxLevel, maxValue);
    }

    // for test
    public static TreeNode generate(int level, int maxLevel, int maxValue) {
        if (level > maxLevel || Math.random() < 0.2) {
            return null;
        }
        TreeNode head = new TreeNode((int) (Math.random() * maxValue));
        head.left = generate(level + 1, maxLevel, maxValue);
        head.right = generate(level + 1, maxLevel, maxValue);
        return head;
    }

    public static void horizontalPrintBinaryTree(TreeNode head) {
        System.out.println("============================Binary Tree:============================");
        if (head == null) {
            return;
        }
        // 中序遍历递归打印当前节点
        horizontalPrintInOrder(head, 0, "H", 17);
        System.out.println("====================================================================");
    }

    private static void horizontalPrintInOrder(TreeNode head, int height, String to, int len) {
        if (head == null) {
            return;
        }
        // 先打印其右子树,这样可以确定到第一行,避免打印到最后打印不上去了
        horizontalPrintInOrder(head.right, height + 1, "v", len);
        // 打印当前节点
        String val = to + head.val + to;
        int lenM = val.length();
        int lenL = (len - lenM) / 2;
        int lenR = len - lenM - lenL;
        val = getSpace(lenL) + val + getSpace(lenR);
        System.out.println(getSpace(height * len) + val);
        // 打印其左子树
        horizontalPrintInOrder(head.left, height + 1, "^", len);
    }

    private static String getSpace(int num) {
        String space = " ";
        StringBuffer buf = new StringBuffer("");
        for (int i = 0; i < num; i++) {
            buf.append(space);
        }
        return buf.toString();
    }
}

2.8、求二叉树中两个节点的最低公共祖先

求二叉树中两个节点的最低公共祖先:给定一棵二叉树的头节点head,和另外两个节点a和b,返回a和b的最低公共祖先。

利用二叉树的递归套路求解最低公共祖先:

思路:

  • 如果给定一个节点,判断该节点为头的子树有没有发现a和b,如果发现了,则返回该节点,否则返回null。
  • 因为是递归,所以会从下往上返回结果,只要找到了a和b,就直接往上返回即可。
  • 所以向其左右子树要的信息就是是否发现了a和b,以及最低公共祖先。
  • 当前节点的判断逻辑是:
  • 如果左右子树有发现了a和b,而且有汇聚点,就直接返回汇聚点。
  • 如果左树发现了一个,右树发现了另一个,那么当前节点就是最低公共祖先。
  • 如果当前节点本身就是a或者b,且左树或者右树发现了另一个,则当前节点就是最低公共祖先。
  • 其他情况返回公共祖先为null。
java 复制代码
    /**
     * 利用二叉树的递归套路求解最低公共祖先:
     * 思路:
     * 如果给定一个节点,判断该节点为头的子树有没有发现a和b,如果发现了,则返回该节点,否则返回null。
     * 因为是递归,所以会从下往上返回结果,只要找到了a和b,就直接往上返回即可。
     * 所以向其左右子树要的信息就是是否发现了a和b,以及最低公共祖先。
     * 当前节点的判断逻辑是:
     * 如果左右子树有发现了a和b,而且有汇聚点,就直接返回汇聚点。
     * 如果左树发现了一个,右树发现了另一个,那么当前节点就是最低公共祖先。
     * 如果当前节点本身就是a或者b,且左树或者右树发现了另一个,则当前节点就是最低公共祖先。
     * 其他情况返回公共祖先为null。
     */
    public static Node lowestAncestor(Node head, Node a, Node b) {
        return lowestAncestorProcess(head, a, b).ans;
    }

    /**
     * 递归套路的处理函数
     */
    private static Info lowestAncestorProcess(Node node, Node nodeA, Node nodeB) {
        // 递归结束条件
        if (node == null) {
            return new Info(false, false, null);
        }
        // 获取左子树和右子树的信息
        Info leftInfo = lowestAncestorProcess(node.left, nodeA, nodeB);
        Info rightInfo = lowestAncestorProcess(node.right, nodeA, nodeB);
        // 如果左右子树有发现了a和b,而且有汇聚点,就直接返回汇聚点
        if (leftInfo.findA && leftInfo.findB) {
            return leftInfo;
        }
        if (rightInfo.findA && rightInfo.findB) {
            return rightInfo;
        }
        // 到这里,代表左右子树没有完整发现信息,就要看当前节点是不是公共祖先
        // 当前节点有公共祖先的情况有两种,一种是当前节点本身是a或者b,且左树或者右树发现了另一个
        // 另一种是当前节点左右子树分别发现了a和b
        boolean findA = (node == nodeA) || leftInfo.findA || rightInfo.findA;
        boolean findB = (node == nodeB) || leftInfo.findB || rightInfo.findB;
        // 如果当前节点左右子树分别发现了a和b,那么当前节点就是最低公共祖先
        if (findA && findB) {
            return new Info(findA, findB, node);
        }
        // 其他情况返回公共祖先为null
        return new Info(findA, findB, null);
    }

    /**
     * 递归套路的信息类
     */
    public static class Info {
        public boolean findA;
        public boolean findB;
        public Node ans;

        public Info(boolean findA, boolean findB, Node ans) {
            this.findA = findA;
            this.findB = findB;
            this.ans = ans;
        }
    }

暴力解法的对数器:

  • 先遍历整棵树,记录每个节点的父节点,然后从nodeA开始向上遍历,记录所有遍历到的节点,
  • 最后从nodeB开始向上遍历,第一个出现在nodeASet中的节点就是最低公共祖先。
java 复制代码
    /**
     * 暴力解法的对数器:
     * 先遍历整棵树,记录每个节点的父节点,然后从nodeA开始向上遍历,记录所有遍历到的节点,
     * 最后从nodeB开始向上遍历,第一个出现在nodeASet中的节点就是最低公共祖先。
     */
    public static Node lowestAncestorComparator(Node node, Node nodeA, Node nodeB) {
        if (node == null || nodeA == null || nodeB == null) {
            return null;
        }
        // 遍历整棵树,记录每个节点的父节点对应关系
        Map<Node, Node> parentMap = new HashMap<>();
        parentMap.put(node, null);
        fillParentMap(node, parentMap);
        // 从nodeA往上遍历,并记录到nodeASet中
        Set<Node> nodeASet = new HashSet<>();
        Node cur = nodeA;
        nodeASet.add(cur);
        while (parentMap.get(cur) != null) {
            cur = parentMap.get(cur);
            nodeASet.add(cur);
        }
        // 从nodeB往上遍历,第一个出现在nodeASet中的节点就是最低公共祖先
        cur = nodeB;
        while (!nodeASet.contains(cur)) {
            cur = parentMap.get(cur);
        }
        return cur;
    }

    public static void fillParentMap(Node node, Map<Node, Node> parentMap) {
        if (node.left != null) {
            parentMap.put(node.left, node);
            fillParentMap(node.left, parentMap);
        }
        if (node.right != null) {
            parentMap.put(node.right, node);
            fillParentMap(node.right, parentMap);
        }
    }

整体代码和测试如下:

java 复制代码
import java.util.*;

/**
 * 求二叉树中两个节点的最低公共祖先
 * 给定一棵二叉树的头节点head,和另外两个节点a和b,返回a和b的最低公共祖先。
 */
public class BinaryTreeLowestAncestor {

    /**
     * 二叉树节点
     */
    public static class Node {
        public int value;
        public Node left;
        public Node right;

        public Node(int data) {
            this.value = data;
        }
    }

    /**
     * 利用二叉树的递归套路求解最低公共祖先:
     * 思路:
     * 如果给定一个节点,判断该节点为头的子树有没有发现a和b,如果发现了,则返回该节点,否则返回null。
     * 因为是递归,所以会从下往上返回结果,只要找到了a和b,就直接往上返回即可。
     * 所以向其左右子树要的信息就是是否发现了a和b,以及最低公共祖先。
     * 当前节点的判断逻辑是:
     * 如果左右子树有发现了a和b,而且有汇聚点,就直接返回汇聚点。
     * 如果左树发现了一个,右树发现了另一个,那么当前节点就是最低公共祖先。
     * 如果当前节点本身就是a或者b,且左树或者右树发现了另一个,则当前节点就是最低公共祖先。
     * 其他情况返回公共祖先为null。
     */
    public static Node lowestAncestor(Node head, Node a, Node b) {
        return lowestAncestorProcess(head, a, b).ans;
    }

    /**
     * 递归套路的处理函数
     */
    private static Info lowestAncestorProcess(Node node, Node nodeA, Node nodeB) {
        // 递归结束条件
        if (node == null) {
            return new Info(false, false, null);
        }
        // 获取左子树和右子树的信息
        Info leftInfo = lowestAncestorProcess(node.left, nodeA, nodeB);
        Info rightInfo = lowestAncestorProcess(node.right, nodeA, nodeB);
        // 如果左右子树有发现了a和b,而且有汇聚点,就直接返回汇聚点
        if (leftInfo.findA && leftInfo.findB) {
            return leftInfo;
        }
        if (rightInfo.findA && rightInfo.findB) {
            return rightInfo;
        }
        // 到这里,代表左右子树没有完整发现信息,就要看当前节点是不是公共祖先
        // 当前节点有公共祖先的情况有两种,一种是当前节点本身是a或者b,且左树或者右树发现了另一个
        // 另一种是当前节点左右子树分别发现了a和b
        boolean findA = (node == nodeA) || leftInfo.findA || rightInfo.findA;
        boolean findB = (node == nodeB) || leftInfo.findB || rightInfo.findB;
        // 如果当前节点左右子树分别发现了a和b,那么当前节点就是最低公共祖先
        if (findA && findB) {
            return new Info(findA, findB, node);
        }
        // 其他情况返回公共祖先为null
        return new Info(findA, findB, null);
    }

    /**
     * 递归套路的信息类
     */
    public static class Info {
        public boolean findA;
        public boolean findB;
        public Node ans;

        public Info(boolean findA, boolean findB, Node ans) {
            this.findA = findA;
            this.findB = findB;
            this.ans = ans;
        }
    }

    /**
     * 暴力解法的对数器:
     * 先遍历整棵树,记录每个节点的父节点,然后从nodeA开始向上遍历,记录所有遍历到的节点,
     * 最后从nodeB开始向上遍历,第一个出现在nodeASet中的节点就是最低公共祖先。
     */
    public static Node lowestAncestorComparator(Node node, Node nodeA, Node nodeB) {
        if (node == null || nodeA == null || nodeB == null) {
            return null;
        }
        // 遍历整棵树,记录每个节点的父节点对应关系
        Map<Node, Node> parentMap = new HashMap<>();
        parentMap.put(node, null);
        fillParentMap(node, parentMap);
        // 从nodeA往上遍历,并记录到nodeASet中
        Set<Node> nodeASet = new HashSet<>();
        Node cur = nodeA;
        nodeASet.add(cur);
        while (parentMap.get(cur) != null) {
            cur = parentMap.get(cur);
            nodeASet.add(cur);
        }
        // 从nodeB往上遍历,第一个出现在nodeASet中的节点就是最低公共祖先
        cur = nodeB;
        while (!nodeASet.contains(cur)) {
            cur = parentMap.get(cur);
        }
        return cur;
    }

    public static void fillParentMap(Node node, Map<Node, Node> parentMap) {
        if (node.left != null) {
            parentMap.put(node.left, node);
            fillParentMap(node.left, parentMap);
        }
        if (node.right != null) {
            parentMap.put(node.right, node);
            fillParentMap(node.right, parentMap);
        }
    }

    public static void main(String[] args) {
        int maxLevel = 7;
        int maxValue = 100;
        int testTimes = 5000000;
        System.out.println("测试开始");
        boolean succeed = true;
        for (int i = 0; i < testTimes; i++) {
            Node head = generateRandomBinaryTree(maxLevel, maxValue);
            //horizontalPrintBinaryTree(head);
            Node nodeA = pickRandomOne(head);
            Node nodeB = pickRandomOne(head);
            if (head == null || nodeA == null || nodeB == null) {
                continue;
            }
            Node lowestAncestor = lowestAncestor(head, nodeA, nodeB);
            Node lowestAncestorComparator = lowestAncestorComparator(head, nodeA, nodeB);
            if (lowestAncestor != lowestAncestorComparator) {
                succeed = false;
                horizontalPrintBinaryTree(head);
                String lowestAncestorStr = lowestAncestor != null ? lowestAncestor.value + "" : "null";
                String lowestAncestorComparatorStr = lowestAncestorComparator != null ? lowestAncestorComparator.value + "" : "null";
                System.out.printf("lowestAncestor: %s, lowestAncestorComparator: %s%n", lowestAncestorStr, lowestAncestorComparatorStr);
                break;
            }
        }
        System.out.println(succeed ? "successful!" : "error!");
    }

    // for test
    public static Node pickRandomOne(Node head) {
        if (head == null) {
            return null;
        }
        ArrayList<Node> arr = new ArrayList<>();
        fillPrelist(head, arr);
        int randomIndex = (int) (Math.random() * arr.size());
        return arr.get(randomIndex);
    }

    // for test
    public static void fillPrelist(Node head, ArrayList<Node> arr) {
        if (head == null) {
            return;
        }
        arr.add(head);
        fillPrelist(head.left, arr);
        fillPrelist(head.right, arr);
    }

    // for test
    public static Node generateRandomBinaryTree(int maxLevel, int maxValue) {
        return generate(1, maxLevel, maxValue);
    }

    // for test
    public static Node generate(int level, int maxLevel, int maxValue) {
        if (level > maxLevel || Math.random() < 0.2) {
            return null;
        }
        Node head = new Node((int) (Math.random() * maxValue));
        head.left = generate(level + 1, maxLevel, maxValue);
        head.right = generate(level + 1, maxLevel, maxValue);
        return head;
    }

    public static void horizontalPrintBinaryTree(Node head) {
        System.out.println("============================Binary Tree:============================");
        if (head == null) {
            return;
        }
        // 中序遍历递归打印当前节点
        horizontalPrintInOrder(head, 0, "H", 17);
        System.out.println("====================================================================");
    }

    private static void horizontalPrintInOrder(Node head, int height, String to, int len) {
        if (head == null) {
            return;
        }
        // 先打印其右子树,这样可以确定到第一行,避免打印到最后打印不上去了
        horizontalPrintInOrder(head.right, height + 1, "v", len);
        // 打印当前节点
        String val = to + head.value + to;
        int lenM = val.length();
        int lenL = (len - lenM) / 2;
        int lenR = len - lenM - lenL;
        val = getSpace(lenL) + val + getSpace(lenR);
        System.out.println(getSpace(height * len) + val);
        // 打印其左子树
        horizontalPrintInOrder(head.left, height + 1, "^", len);
    }

    private static String getSpace(int num) {
        String space = " ";
        StringBuffer buf = new StringBuffer("");
        for (int i = 0; i < num; i++) {
            buf.append(space);
        }
        return buf.toString();
    }
}

2.9、派对的最大快乐值

派对的最大快乐值:

  • 员工信息的定义如下:
    class Employee {
      public int happy; // 这名员工可以带来的快乐值
      List subordinates; // 这名员工有哪些直接下级
    }
  • 公司的每个员工都符合 Employee 类的描述。整个公司的人员结构可以看作是一棵标准的、 没有环的多叉树。
  • 树的头节点是公司唯一的老板。除老板之外的每个员工都有唯一的直接上级。
  • 叶节点是没有任何下属的基层员工(subordinates列表为空),除基层员工外,每个员工都有一个或多个直接下级。
  • 派对的最大快乐值
  • 这个公司现在要办party,你可以决定哪些员工来,哪些员工不来,规则:
  • 1.如果某个员工来了,那么这个员工的所有直接下级都不能来
  • 2.派对的整体快乐值是所有到场员工快乐值的累加
  • 3.你的目标是让派对的整体快乐值尽量大
  • 给定一棵多叉树的头节点boss,请返回派对的最大快乐值。

利用递归套路求解派对的最大快乐值:

思路:

  • 对于每一个员工x,可以选择来或者不来两种情况,分别计算出x来和不来的最大快乐值,然后取较大的一个,就是整体最大的快乐值。
  • 递归函数的定义:
    1. 如果x来的话,那么x的快乐值就是x.happy,然后累加所有的直接下级不来的快乐值。
    1. 如果x不来的话,那么x的快乐值就是0,然后累加所有的直接下级来和不来的快乐值的较大值。
  • 整体递归结束,返回的结果就是根节点来和不来的较大值。
java 复制代码
    /**
     * 利用递归套路求解派对的最大快乐值:
     * 思路:
     * 对于每一个员工x,可以选择来或者不来两种情况,分别计算出x来和不来的最大快乐值,然后取较大的一个,就是整体最大的快乐值。
     * 递归函数的定义:
     * 1. 如果x来的话,那么x的快乐值就是x.happy,然后累加所有的直接下级不来的快乐值。
     * 2. 如果x不来的话,那么x的快乐值就是0,然后累加所有的直接下级来和不来的快乐值的较大值。
     * 整体递归结束,返回的结果就是根节点来和不来的较大值。
     */
    public static int maxHappy(Employee head) {
        if (head == null) {
            return 0;
        }
        // 获取到当前节点的结果信息
        Info info = maxHappyProcess(head);
        return Math.max(info.no, info.yes);

    }

    /**
     * 递归处理函数,返回当前节点的信息
     */
    private static Info maxHappyProcess(Employee node) {
        // 如果为null,返回结果为0的信息
        if (node == null) {
            return new Info(0, 0);
        }
        // 分别统计当前节点来和不来的信息
        int no = 0;
        int yes = node.happy;
        for (Employee next : node.nexts) {
            // 得到当前员工的信息
            Info nextInfo = maxHappyProcess(next);
            // 如果当前节点来,那么下个节点就要统计不来的信息
            yes += nextInfo.no;
            // 如果当前节点不来,那么下个节点可以来也可以不来,取较大值
            no += Math.max(nextInfo.no, nextInfo.yes);
        }
        // 返回当前节点来和不来的信息
        return new Info(no, yes);
    }

    /**
     * 递归套路的返回信息
     */
    public static class Info {
        public int no;
        public int yes;

        public Info(int no, int yes) {
            this.no = no;
            this.yes = yes;
        }
    }

暴力方法求解的对数器:

思路:

  • 对于每一个员工来说,有两种情况:
  • 1、 上层来的话,那么当前员工不能来,整体的快乐值就是当前为上层的员工不来的时候,下层的员工的所有选择的快乐值的累加。
  • 2、 上层员工不来的话,那么当前员工可以来也可以不来:
  • 2.1、当前员工来的话,快乐值就是当前员工的快乐值,然后累计当前员工作为上层来的情况下,所有直接下级的累加。
  • 2.2、当前员工不来的话,当前的快乐值就是0,然后累计当前员工作为上层不来的情况下,所有直接下级来和不来的快乐值的较大值。
  • 本质就是利用递归的方式,从下往上累加,然后取较大的一个。
java 复制代码
    /**
     * 暴力方法求解的对数器:
     * 思路:
     * 对于每一个员工来说,有两种情况:
     * 1、 上层来的话,那么当前员工不能来,整体的快乐值就是当前为上层的员工不来的时候,下层的员工的所有选择的快乐值的累加。
     * 2、 上层员工不来的话,那么当前员工可以来也可以不来:
     * 2.1、当前员工来的话,快乐值就是当前员工的快乐值,然后累计当前员工作为上层来的情况下,所有直接下级的累加。
     * 2.2、当前员工不来的话,当前的快乐值就是0,然后累计当前员工作为上层不来的情况下,所有直接下级来和不来的快乐值的较大值。
     * 本质就是利用递归的方式,从下往上累加,然后取较大的一个。
     */
    public static int maxHappyComparator(Employee boss) {
        if (boss == null) {
            return 0;
        }
        // boss没有上级,所以开始就是上级不来的情况
        return maxHappyComparatorProcess(boss, false);
    }

    /**
     * 递归函数的含义:
     * 当前来到的节点叫cur,up表示cur的上级是否来,
     * 如果up为true,表示在cur上级已经确定来,的情况下,cur整棵树能够提供最大的快乐值
     * 如果up为false,表示在cur上级已经确定不来,的情况下,cur整棵树能够提供最大的快乐值
     */
    private static int maxHappyComparatorProcess(Employee cur, boolean up) {
        if (cur == null) {
            return 0;
        }
        // 如果up来,那当前员工就不能来,直接累加下层员工的信息即可
        if (up) {
            int ans = 0;
            for (Employee next : cur.nexts) {
                ans += maxHappyComparatorProcess(next, false);
            }
            return ans;
        }
        // 如果up不来,那当前员工可以选择来,也可以选择不来,最大快乐值区这两种的较大值即可
        int yes = cur.happy;
        int no = 0;
        for (Employee next : cur.nexts) {
            yes += maxHappyComparatorProcess(next, true);
            no += maxHappyComparatorProcess(next, false);
        }
        return Math.max(yes, no);
    }

整体代码和测试如下:

java 复制代码
import java.util.ArrayList;
import java.util.List;

/**
 * 派对的最大快乐值
 * <p>
 * 员工信息的定义如下:
 * class Employee {
 * public int happy; // 这名员工可以带来的快乐值
 * List<Employee> subordinates; // 这名员工有哪些直接下级
 * }
 * 公司的每个员工都符合 Employee 类的描述。整个公司的人员结构可以看作是一棵标准的、 没有环的多叉树。
 * 树的头节点是公司唯一的老板。除老板之外的每个员工都有唯一的直接上级。
 * 叶节点是没有任何下属的基层员工(subordinates列表为空),除基层员工外,每个员工都有一个或多个直接下级。
 * <p>
 * 派对的最大快乐值
 * 这个公司现在要办party,你可以决定哪些员工来,哪些员工不来,规则:
 * 1.如果某个员工来了,那么这个员工的所有直接下级都不能来
 * 2.派对的整体快乐值是所有到场员工快乐值的累加
 * 3.你的目标是让派对的整体快乐值尽量大
 * 给定一棵多叉树的头节点boss,请返回派对的最大快乐值。
 *
 */
public class MaxHappy {
    /**
     * 员工信息定义类
     */
    public static class Employee {
        public int happy;
        public List<Employee> nexts;

        public Employee(int happy) {
            this.happy = happy;
            this.nexts = new ArrayList<>();
        }
    }

    /**
     * 利用递归套路求解派对的最大快乐值:
     * 思路:
     * 对于每一个员工x,可以选择来或者不来两种情况,分别计算出x来和不来的最大快乐值,然后取较大的一个,就是整体最大的快乐值。
     * 递归函数的定义:
     * 1. 如果x来的话,那么x的快乐值就是x.happy,然后累加所有的直接下级不来的快乐值。
     * 2. 如果x不来的话,那么x的快乐值就是0,然后累加所有的直接下级来和不来的快乐值的较大值。
     * 整体递归结束,返回的结果就是根节点来和不来的较大值。
     */
    public static int maxHappy(Employee head) {
        if (head == null) {
            return 0;
        }
        // 获取到当前节点的结果信息
        Info info = maxHappyProcess(head);
        return Math.max(info.no, info.yes);

    }

    /**
     * 递归处理函数,返回当前节点的信息
     */
    private static Info maxHappyProcess(Employee node) {
        // 如果为null,返回结果为0的信息
        if (node == null) {
            return new Info(0, 0);
        }
        // 分别统计当前节点来和不来的信息
        int no = 0;
        int yes = node.happy;
        for (Employee next : node.nexts) {
            // 得到当前员工的信息
            Info nextInfo = maxHappyProcess(next);
            // 如果当前节点来,那么下个节点就要统计不来的信息
            yes += nextInfo.no;
            // 如果当前节点不来,那么下个节点可以来也可以不来,取较大值
            no += Math.max(nextInfo.no, nextInfo.yes);
        }
        // 返回当前节点来和不来的信息
        return new Info(no, yes);
    }

    /**
     * 递归套路的返回信息
     */
    public static class Info {
        public int no;
        public int yes;

        public Info(int no, int yes) {
            this.no = no;
            this.yes = yes;
        }
    }

    /**
     * 暴力方法求解的对数器:
     * 思路:
     * 对于每一个员工来说,有两种情况:
     * 1、 上层来的话,那么当前员工不能来,整体的快乐值就是当前为上层的员工不来的时候,下层的员工的所有选择的快乐值的累加。
     * 2、 上层员工不来的话,那么当前员工可以来也可以不来:
     * 2.1、当前员工来的话,快乐值就是当前员工的快乐值,然后累计当前员工作为上层来的情况下,所有直接下级的累加。
     * 2.2、当前员工不来的话,当前的快乐值就是0,然后累计当前员工作为上层不来的情况下,所有直接下级来和不来的快乐值的较大值。
     * 本质就是利用递归的方式,从下往上累加,然后取较大的一个。
     */
    public static int maxHappyComparator(Employee boss) {
        if (boss == null) {
            return 0;
        }
        // boss没有上级,所以开始就是上级不来的情况
        return maxHappyComparatorProcess(boss, false);
    }

    /**
     * 递归函数的含义:
     * 当前来到的节点叫cur,up表示cur的上级是否来,
     * 如果up为true,表示在cur上级已经确定来,的情况下,cur整棵树能够提供最大的快乐值
     * 如果up为false,表示在cur上级已经确定不来,的情况下,cur整棵树能够提供最大的快乐值
     */
    private static int maxHappyComparatorProcess(Employee cur, boolean up) {
        if (cur == null) {
            return 0;
        }
        // 如果up来,那当前员工就不能来,直接累加下层员工的信息即可
        if (up) {
            int ans = 0;
            for (Employee next : cur.nexts) {
                ans += maxHappyComparatorProcess(next, false);
            }
            return ans;
        }
        // 如果up不来,那当前员工可以选择来,也可以选择不来,最大快乐值区这两种的较大值即可
        int yes = cur.happy;
        int no = 0;
        for (Employee next : cur.nexts) {
            yes += maxHappyComparatorProcess(next, true);
            no += maxHappyComparatorProcess(next, false);
        }
        return Math.max(yes, no);
    }

    public static void main(String[] args) {
        int maxLevel = 4;
        int maxNexts = 7;
        int maxValue = 100;
        int testTimes = 5000000;
        boolean succeed = true;
        System.out.println("测试开始");
        for (int i = 0; i < testTimes; i++) {
            Employee boss = genarateBoss(maxLevel, maxNexts, maxValue);
            int maxHappy = maxHappy(boss);
            int maxHappyComparator = maxHappyComparator(boss);
            if (maxHappy != maxHappyComparator) {
                succeed = false;
                System.out.printf(" maxHappy: %d, maxHappyComparator: %d%n", maxHappy, maxHappyComparator);
                break;
            }
        }
        System.out.println(succeed ? "successful!" : "error!");
    }

    // for test
    public static Employee genarateBoss(int maxLevel, int maxNexts, int maxHappy) {
        if (Math.random() < 0.02) {
            return null;
        }
        Employee boss = new Employee((int) (Math.random() * (maxHappy + 1)));
        genarateNexts(boss, 1, maxLevel, maxNexts, maxHappy);
        return boss;
    }

    // for test
    public static void genarateNexts(Employee e, int level, int maxLevel, int maxNexts, int maxHappy) {
        if (level > maxLevel) {
            return;
        }
        int nextsSize = (int) (Math.random() * (maxNexts + 1));
        for (int i = 0; i < nextsSize; i++) {
            Employee next = new Employee((int) (Math.random() * (maxHappy + 1)));
            e.nexts.add(next);
            genarateNexts(next, level + 1, maxLevel, maxNexts, maxHappy);
        }
    }


}

后记

个人学习总结笔记,不能保证非常详细,轻喷

相关推荐
武子康3 小时前
Java-136 深入浅出 MySQL Spring Boot @Transactional 使用指南:事务传播、隔离级别与异常回滚策略
java·数据库·spring boot·mysql·性能优化·系统架构·事务
微笑尅乐9 小时前
力扣350.两个数组的交集II
java·算法·leetcode·动态规划
rzjslSe9 小时前
【JavaGuide学习笔记】理解并发(Concurrency)与并行(Parallelism)的区别
java·笔记·学习
青柠编程10 小时前
基于Spring Boot的竞赛管理系统架构设计
java·spring boot·后端
茯苓gao10 小时前
CAN总线学习(四)错误处理 STM32CAN外设一
网络·笔记·stm32·单片机·学习
꒰ঌ 安卓开发໒꒱10 小时前
Java面试-并发面试(二)
java·开发语言·面试
Mr.Aholic10 小时前
Java系列知识之 ~ Spring 与 Spring Boot 常用注解对比说明
java·spring boot·spring
Source.Liu10 小时前
【mdBook】1 安装
笔记·rust·markdown
航Hang*10 小时前
Kurt-Blender零基础教程:第3章:材质篇——第3节:给模型上材质
笔记·blender·材质