二叉树面试专题

一、概念

二、题目

2.1 把数组转换成二叉树

2.2.1 使用队列方式

public static Node getTreeFromArr2(int[] arr) {
        if (arr == null || arr.length == 0) {
            return null;
        }

        LinkedList<Node> quque = new LinkedList<>();
        Node root = new Node(arr[0]);

        quque.add(root);
        int index = 1;
        while(!quque.isEmpty()){
            // pop 0的时候,刚好操作1、2
            // pop 1的时候,刚好操作3、4
            // pop 2的时候,刚好操作5、6
            Node temp = quque.pop();
            if(index < arr.length){
                temp.left = new Node(arr[index]);
                quque.add(temp.left);
                index++;
            }

            if(index < arr.length){
                temp.right = new Node(arr[index]);
                quque.add(temp.right);
                index++;
            }
        }
        return root;
    }

2.2.3 使用堆的方式

private static Node getTreeFromArr(int[] arr) {
        Map<Integer, Node> map = new HashMap();
        for (int i = arr.length - 1; i >= 0; i--) {
            Node temp = new Node(arr[i]);
            temp.value = arr[i];
            map.put(i, temp);
            int leftChildIndex = 2 * i + 1;
            int rightChildIndex = 2 * i + 2;
            if (leftChildIndex < arr.length) {
                temp.left = map.get(leftChildIndex);
            }
            if (rightChildIndex < arr.length) {
                temp.right = map.get(rightChildIndex);
            }
        }
        return map.get(0);
    }

2.2 二叉树进行前序、中序、后序遍历

2.2.0 递归的本质是 Stack

2.2.1 递归的方式

@ToString
    public static class Node {
        public Node left;
        public Node right;
        public int value;

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

        }
    }

    public static void main(String[] args) {
        int[] arr = {0, 1, 2, 3, 4, 5, 6};
//        Node head = getTreeFromArr(arr);
        Node head2 = getTreeFromArr2(arr);
//        System.out.println(JSON.toJSONString(head));
        System.out.println(JSON.toJSONString(head2));

        /*
            前序遍历:[0, 1, 3, 4, 2, 5, 6]
            中序遍历:[3, 1, 4, 0, 5, 2, 6]
            后序遍历:[3, 4, 1, 5, 6, 2, 0]
         */
        preorderPrint(head2);
        System.out.println();
        inorderPrint(head2);
        System.out.println();
        postorderPrint(head2);
    }

    /**
     * 前序遍历(中左右)
     */
    public static void preorderPrint(Node temp){
        if(null == temp){
            return;
        }
        System.out.print(temp.value + ",");
        preorderPrint(temp.left);
        preorderPrint(temp.right);
    }

    /**
     * 中序遍历(左中右)
     */
    public static void inorderPrint(Node temp){
        if(null == temp){
            return;
        }
        inorderPrint(temp.left);
        System.out.print(temp.value + ",");
        inorderPrint(temp.right);
    }
    /**
     * 右序遍历(左中右)
     */
    public static void postorderPrint(Node temp){
        if(null == temp){
            return;
        }
        postorderPrint(temp.left);
        postorderPrint(temp.right);
        System.out.print(temp.value + ",");
    }


    public static Node getTreeFromArr2(int[] arr) {
        if (arr == null || arr.length == 0) {
            return null;
        }

        LinkedList<Node> quque = new LinkedList<>();
        Node root = new Node(arr[0]);

        quque.add(root);
        int index = 1;
        while(!quque.isEmpty()){
            // pop 0的时候,刚好操作1、2
            // pop 1的时候,刚好操作3、4
            // pop 2的时候,刚好操作5、6
            Node temp = quque.pop();
            if(index < arr.length){
                temp.left = new Node(arr[index]);
                quque.add(temp.left);
                index++;
            }

            if(index < arr.length){
                temp.right = new Node(arr[index]);
                quque.add(temp.right);
                index++;
            }
        }
        return root;
    }

2.2.2 栈的方式

1、前序遍历(宽度遍历)

/**
     * Stack方法
     * 前序遍历(中左右)
     */
    public static void preorderPrintByStack(Node root) {
        Stack<Node> stack = new Stack<>();
        stack.push(root);

        // 可以不用queue,可以直接打印,省空间 N
        // 我加一个 queue的目的是为了后序的翻转好理解
        Queue<Node> queue = new LinkedList<>();

        while (!stack.isEmpty()) {
            Node temp = stack.pop();
//            System.out.print(temp.value + ",");
            // 此处可以不打印,直接入一个新的 queue
            queue.offer(temp);
            if (temp.right != null) {
                stack.push(temp.right);
            }
            if (temp.left != null) {
                stack.push(temp.left);
            }
        }
        System.out.print("preorderPrintByStack -> ");
        while (!queue.isEmpty()) {
            System.out.print(queue.poll().value + ",");
        }
        System.out.println();
    }
/**
     * 宽度遍历,其实就是中左右,也就是前序遍历
     * 用一个 queue 辅助也是可以搞定的
     * @param head
     */
    public static void widthPrint(Node head){
        if(null == head){
            return;
        }
        Queue<Node> queue = new LinkedList<>();
        queue.offer(head);

        while(!queue.isEmpty()){
            Node temp = queue.poll();
            System.out.print(temp.value + ",");
            if(temp.left != null){
                queue.offer(temp.left);
            }
            if(temp.right != null){
                queue.offer(temp.right);
            }
        }
    }
2、中序遍历


    /**
     * Stack方法
     * 中序遍历(左中右)
     */
    public static void inOrderPrintByStack(Node root) {
        if(null == root){
            return;
        }
        Node current = root;
        Stack<Node> stack = new Stack<>();

        // 可以不用queue,可以直接打印,省空间 N
        // 我加一个 queue的目的是为了后序的翻转好理解
        Queue<Node> queue = new LinkedList<>();

        while (current != null || !stack.isEmpty()) {
            // 如果current不是null,那么就一直往左找下去
            while(current != null){
                stack.push(current);
                current = current.left;
            }
            current = stack.pop();
//            System.out.print(current.value + ",");
            queue.offer(current);
            // 左边已经干完了,那么就一直往右找
            current = current.right;
        }

        System.out.print("inOrderPrintByStack -> ");
        while (!queue.isEmpty()) {
            System.out.print(queue.poll().value + ",");
        }
    }
3、右序遍历

/**
     * 右序遍历(左右中)
     * 翻过来就是 中右左, 因为前序是中左右,前序是先压right再压left,那么反过来,先压left再压right的话,就会是中右左了
     */
    public static void postorderPrintByStack(Node temp) {
        if (null == temp) {
            return;
        }
        Stack<Node> stackA = new Stack<>();
        stackA.push(temp);

        // 注意和之前的区别,上面的中序和前序都是 queue ,FIFO,那么 Stack 的FILO,就能实现这个翻转
        Stack<Node> stackB = new Stack<>();

        while(!stackA.isEmpty()){
            temp = stackA.pop();
            stackB.push(temp);
            if(temp.left != null){
                stackA.push(temp.left);
            }
            if(temp.right != null){
                stackA.push(temp.right);
            }
        }

        System.out.print("postorderPrintByStack -> ");
        while (!stackB.isEmpty()) {
            System.out.print(stackB.pop().value + ",");
        }
    }
0、测试代码汇总
@ToString
    public static class Node {
        public Node left;
        public Node right;
        public int value;

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

        }
    }

    public static void main(String[] args) {
        int[] arr = {0, 1, 2, 3, 4, 5, 6};
//        Node head = getTreeFromArr(arr);
        Node head2 = getTreeFromArr2(arr);
//        System.out.println(JSON.toJSONString(head));
        System.out.println(JSON.toJSONString(head2));

        /*
            前序遍历:[0, 1, 3, 4, 2, 5, 6]
            中序遍历:[3, 1, 4, 0, 5, 2, 6]
            后序遍历:[3, 4, 1, 5, 6, 2, 0]
         */

        System.out.println("preorderPrint ->");
        preorderPrint(head2);
        System.out.println();

        System.out.println("inorderPrint ->");
        inorderPrint(head2);
        System.out.println();

        System.out.println("postorderPrint ->");
        postorderPrint(head2);
        System.out.println();
        System.out.println("——————————");


        preorderPrintByStack(head2);
        inOrderPrintByStack(head2);
        postorderPrintByStack(head2);
    }

    /**
     * 前序遍历(中左右)
     */
    public static void preorderPrint(Node temp) {
        if (null == temp) {
            return;
        }
        System.out.print(temp.value + ",");
        preorderPrint(temp.left);
        preorderPrint(temp.right);
    }

    /**
     * 中序遍历(左中右)
     */
    public static void inorderPrint(Node temp) {
        if (null == temp) {
            return;
        }
        inorderPrint(temp.left);
        System.out.print(temp.value + ",");
        inorderPrint(temp.right);
    }

    /**
     * 右序遍历(左中右)
     */
    public static void postorderPrint(Node temp) {
        if (null == temp) {
            return;
        }
        postorderPrint(temp.left);
        postorderPrint(temp.right);
        System.out.print(temp.value + ",");
    }

    /**
     * Stack方法
     * 前序遍历(中左右)
     */
    public static void preorderPrintByStack(Node root) {
        Stack<Node> stack = new Stack<>();
        stack.push(root);

        // 可以不用queue,可以直接打印,省空间 N
        // 我加一个 queue的目的是为了后序的翻转好理解
        Queue<Node> queue = new LinkedList<>();

        while (!stack.isEmpty()) {
            Node temp = stack.pop();
//            System.out.print(temp.value + ",");
            // 此处可以不打印,直接入一个新的 queue
            queue.offer(temp);
            if (temp.right != null) {
                stack.push(temp.right);
            }
            if (temp.left != null) {
                stack.push(temp.left);
            }
        }
        System.out.print("preorderPrintByStack -> ");
        while (!queue.isEmpty()) {
            System.out.print(queue.poll().value + ",");
        }
        System.out.println();
    }

    /**
     * Stack方法
     * 中序遍历(左中右)
     */
    public static void inOrderPrintByStack(Node root) {
        if(null == root){
            return;
        }
        Node current = root;
        Stack<Node> stack = new Stack<>();

        // 可以不用queue,可以直接打印,省空间 N
        // 我加一个 queue的目的是为了后序的翻转好理解
        Queue<Node> queue = new LinkedList<>();

        while (current != null || !stack.isEmpty()) {
            // 如果current不是null,那么就一直往左找下去
            while(current != null){
                stack.push(current);
                current = current.left;
            }
            current = stack.pop();
//            System.out.print(current.value + ",");
            queue.offer(current);
            // 左边已经干完了,那么就一直往右找
            current = current.right;
        }

        System.out.print("inOrderPrintByStack -> ");
        while (!queue.isEmpty()) {
            System.out.print(queue.poll().value + ",");
        }
    }


    /**
     * 右序遍历(左右中)
     * 翻过来就是 中右左, 因为前序是中左右,前序是先压right再压left,那么反过来,先压left再压right的话,就会是中右左了
     */
    public static void postorderPrintByStack(Node temp) {
        if (null == temp) {
            return;
        }
        Stack<Node> stackA = new Stack<>();
        stackA.push(temp);

        // 注意和之前的区别,上面的中序和前序都是 queue ,FIFO,那么 Stack 的FILO,就能实现这个翻转
        Stack<Node> stackB = new Stack<>();

        while(!stackA.isEmpty()){
            temp = stackA.pop();
            stackB.push(temp);
            if(temp.left != null){
                stackA.push(temp.left);
            }
            if(temp.right != null){
                stackA.push(temp.right);
            }
        }

        System.out.print("postorderPrintByStack -> ");
        while (!stackB.isEmpty()) {
            System.out.print(stackB.pop().value + ",");
        }
    }




    public static Node getTreeFromArr2(int[] arr) {
        if (arr == null || arr.length == 0) {
            return null;
        }

        LinkedList<Node> quque = new LinkedList<>();
        Node root = new Node(arr[0]);

        quque.add(root);
        int index = 1;
        while (!quque.isEmpty()) {
            // pop 0的时候,刚好操作1、2
            // pop 1的时候,刚好操作3、4
            // pop 2的时候,刚好操作5、6
            Node temp = quque.pop();
            if (index < arr.length) {
                temp.left = new Node(arr[index]);
                quque.add(temp.left);
                index++;
            }

            if (index < arr.length) {
                temp.right = new Node(arr[index]);
                quque.add(temp.right);
                index++;
            }
        }
        return root;
    }

2.3 二叉树 DFS、BFS

深度优先搜索:Depth-First Search (下图打印 0,1,3,4,2,5,6)

广度优先搜索:Breadth-First Search(下图打印 0,1,2,3,4,5,6)

/**
     * 构造一棵二叉树
     */
    public static Node getNormalTree() {
        Node node0 = new Node(0);
        Node node1 = new Node(1);
        Node node2 = new Node(2);
        Node node3 = new Node(3);
        Node node4 = new Node(4);
        Node node5 = new Node(5);
        Node node6 = new Node(6);

        node0.left = node1;
        node0.right = node2;

        node1.left = node3;
        node1.right = node4;

        node2.left = node5;
        node2.right = node6;
        return node0;
    }

1、BFS

同上面的前序遍历

 /**
     * breadth First Search
     */
    private static void bfsPrint(Node head) {
        if (null == head) {
            return;
        }
        System.out.println("bfsPrint ===");
        LinkedList<Node> queue = new LinkedList<>();
        queue.offer(head);
        while (!queue.isEmpty()) {
            int queueSize = queue.size();
            for (int i = 0; i < queueSize; i++) {
                Node temp = queue.poll();
                System.out.print(temp.value + ", ");
                if (temp.left != null) {
                    queue.offer(temp.left);
                }
                if (temp.right != null) {
                    queue.offer(temp.right);
                }
            }
        }
    }

2、DFS(递归)

/**
     * depth First Search
     */
    private static void dfsPrint(Node head) {
        if (null == head) {
            return;
        }
        System.out.print(head.value + ", ");
        dfsPrint(head.left);
        dfsPrint(head.right);
    }

3、DFS(Stack)

/**
     * depth First Search
     */
    private static void dfsPrintByStack(Node head) {
        if (null == head) {
            return;
        }
        System.out.println("dfsPrintByStack === ");
        Node temp = head;
        Stack<Node> stack = new Stack<>();
        stack.push(temp);
        while (!stack.isEmpty()) {
            temp = stack.pop();
            System.out.print(temp.value + ", ");
            if(temp.right != null){
                stack.push(temp.right);// 先压入右子树
            }
            if(temp.left != null){
                stack.push(temp.left);// 再压入左子树,确保左子树先出栈
            }
        }
    }

2.4 求一颗二叉树的宽度、广度

public static Node getSpecialTree() {
        Node node0 = new Node(0);
        Node node1 = new Node(1);
        Node node2 = new Node(2);
        Node node3 = new Node(3);
        Node node4 = new Node(4);
        Node node5 = new Node(5);
        Node node6 = new Node(6);
        Node node7 = new Node(7);
        Node node8 = new Node(8);
        Node node9 = new Node(9);
        Node node10 = new Node(10);
        Node node11 = new Node(11);

        node0.left = node1;
        node0.right = node2;

        node1.left = node3;
        node1.right = node4;

        node2.left = node5;

        node3.left = node6;

        node4.left = node7;
        node4.right = node8;

        node5.right = node9;

        node7.left = node10;
        node7.right = node11;

        return node0;
    }

1、 求宽度

private static void getTreeWidth(Node root) {
        System.out.println();
        System.out.println("---getTreeWidth---");
        Node temp = root;
        Queue<Node> queue = new LinkedList<>();
        queue.offer(temp);

        int maxLevelSize = 0;
        while (!queue.isEmpty()) {
            int levelSize = queue.size();
            maxLevelSize = Math.max(maxLevelSize,levelSize);
            // 思路就是:for完结束之后,才会进入下一层
            for (int i = 0; i < levelSize; i++) {
                temp = queue.poll();
                System.out.print(temp.value + ",");
                if (temp.left != null) {
                    queue.offer(temp.left);

                }
                if (temp.right != null) {
                    queue.offer(temp.right);
                }
            }
        }
        System.out.println("maxLevelSize -> " + maxLevelSize);
    }

2、求广度(递归解决)

/**
     * DFS思想,深度优先检索思想
     */
    public static int calculateDepth(Node temp, int depth) {
        if (null == temp) {
            return depth;
        }
        // 每次加1层
        depth++;
//        System.out.print(temp.value);
        // 找出left最深
        int leftDepth = calculateDepth(temp.left, depth);
        // 找出right最深
        int rightDepth = calculateDepth(temp.right, depth);
        return Math.max(leftDepth, rightDepth);
    }

3、求广度(Stack)

 /**
     * DFS思想,深度优先检索思想
     */
    public static int calculateDepth(Node temp) {
        if (null == temp) {
            return 0;
        }
        Stack<Node> stack = new Stack<>();
        stack.push(temp);
        // 记录每个Node对应的层级
        Map<Node, Integer> node2DepthMap = new HashMap<>();
        node2DepthMap.put(temp, 1);
        while (!stack.isEmpty()) {
            temp = stack.pop();
            int level = node2DepthMap.get(temp);
//            System.out.print(temp.value + ", ");
            if (temp.right != null) {
                stack.push(temp.right);
                node2DepthMap.put(temp.right, level + 1);
            }
            if (temp.left != null) {
                stack.push(temp.left);
                node2DepthMap.put(temp.left, level + 1);
            }
        }
        for (int levelCount :
                node2DepthMap.values()) {
            System.out.println(levelCount);
        }
        return Collections.max(node2DepthMap.values());
    }

2.5 判断一棵树是不是搜索二叉树 BST

/**
     * 构造一棵二叉树
     */
    public static Node getBSTTree() {
        Node node5 = new Node(5);
        Node node4 = new Node(4);
        Node node7 = new Node(7);
        Node node2 = new Node(2);
        Node node6 = new Node(6);
        Node node8 = new Node(8);
        Node node1 = new Node(1);
        Node node3 = new Node(3);

        node5.left = node4;
        node5.right = node7;

        node4.left = node2;
        node4.right = null;

        node2.left = node1;
        node2.right = node3;

        node7.left = node6;
        node7.right = node8;

        return node5;
    }

1、用递归的方法(用辅助队列)

思路:采用中序遍历的方法,把原本要print的所有元素,放到queue里面,FIFO,然后判断相邻两个元素大小即可

这个方法需要用到一个 queue 做辅助空间

private static boolean checkBST(Node head) {
        LinkedList<Node> queue = new LinkedList<>();

        // 使用queue辅助空间
        checkBSTWithQueue(head, queue);

        // 可以打印看看结果
//        for (int i = 0; i < queue.size(); i++) {
//            System.out.print(queue.get(i).value + ",");
//        }

        int preNodeValue = queue.poll().value;
        while (!queue.isEmpty()){
            Node temp = queue.poll();
            if(preNodeValue > temp.value){
                return false;
            }
            preNodeValue = temp.value;

        }
        return true;
    }



    /**
     * 1、如何判断一棵树是不是搜索二叉树?
     * 思路:左中右,那么就是中序遍历
     */
    private static void checkBSTWithQueue(Node head, Queue<Node> queue) {
        if (null == head) {
            return;
        }
        //判断左边是不是 BST
        checkBSTWithQueue(head.left, queue);
        queue.offer(head);

        //判断右边是不是 BST
        checkBSTWithQueue(head.right, queue);
    }

2、用递归的方法(不用辅助队列)


    // 说下为什么不用全局变量,这玩意线程不安全
    private static int preValue = Integer.MIN_VALUE;

    private static class Wrapper{
        int value;
        Wrapper(int value){
            this.value = value;
        }
    }

    private static boolean checkBST(Node head) {
        return checkBSTHelper(head,new Wrapper(Integer.MIN_VALUE));
    }

    /**
     * 1、如何判断一棵树是不是搜索二叉树?
     * 思路:左中右,那么就是中序遍历
     */
    private static boolean checkBSTHelper(Node head, Wrapper preValue) {
        if (null == head) {
            return true;
        }
        // 判断左边是不是 BST
        if (!checkBSTHelper(head.left, preValue)) {
            return false;
        }

//        System.out.println(head.value); // 这句如果是打印,那么就是 前序遍历输出
        // 判断当前
        if (preValue.value > head.value) {
            return false;
        }

        // 第一轮,到最左下角的时候,会把值赋给 preValue,作为有意义的第一个值(因为本来中序遍历的逻辑是print这个value)
        preValue.value = head.value;

        // 判断右边是不是 BST
        return checkBSTHelper(head.right, preValue);
    }

3、用递归的方法(用一个ReturnData)

    @AllArgsConstructor
    @NoArgsConstructor
    private static class BSTReturnData {
        private int min;
        private int max;
        private boolean isBST;
    }

    private static boolean checkBST3(Node temp) {
        if (temp == null) {
            return true;
        }
        return processBST(temp).isBST;
    }

    private static BSTReturnData processBST(Node temp) {
        if (temp == null) {
            return new BSTReturnData(Integer.MAX_VALUE, Integer.MIN_VALUE, true);
        }
        BSTReturnData leftReturnData = processBST(temp.left);
        BSTReturnData rightReturnData = processBST(temp.right);

        int min = temp.value;
        int max = temp.value;
        boolean isBST = true;
        // BST的要求: left 小于当前,当前小于 right
        if (leftReturnData != null) {
            min = Math.min(min, leftReturnData.min);
            max = Math.max(max, leftReturnData.max);

            if(!leftReturnData.isBST || leftReturnData.max >= temp.value){
                isBST = false;
            }
        }
        if (rightReturnData != null) {
            min = Math.min(min, rightReturnData.min);
            max = Math.max(max, rightReturnData.max);
            if(!rightReturnData.isBST || rightReturnData.min <= temp.value){
                isBST = false;
            }
        }
        return new BSTReturnData(min, max, isBST);
    }

4、用 Stack 的方法

/**
     * 1、如何判断一棵树是不是搜索二叉树?
     * 思路:左中右,那么就是中序遍历
     * Stack方法
     */
    private static boolean checkBSTHelperWithStack(Node head) {
        if (null == head) {
            return true;
        }
        System.out.println(" === checkBSTHelperWithStack ===");
        Node current = head;

        Stack<Node> stack = new Stack<>();
        stack.push(current);

        int preValue = Integer.MIN_VALUE;
        while (!stack.isEmpty()) {
            while (current != null && current.left != null) {
                stack.push(current.left);
                current = current.left;
            }
            current = stack.pop();
            if(preValue > current.value){
                return false;
            } else {
                preValue = current.value;
            }

            System.out.print(current.value + ", ");

            if (current.right != null) {
                stack.push(current.right);
            }
            current = current.right;

        }
        return true;
    }

2.6 判断一棵树是不是完全二叉树 CBT

complete binary tree 完全二叉树

private static boolean isCompleteBinaryTree(Node root) {
        if(null == root){
            return false;
        }
        System.out.println();
        System.out.println("isCompleteBinaryTree --- begin ---");
        System.out.println();
        LinkedList<Node> queue = new LinkedList<>();
        queue.offer(root);
        boolean shouldNoChild = false;
        while(!queue.isEmpty()){
            int queueSize = queue.size();
            for (int i = 0; i < queueSize; i++) {
                Node temp = queue.poll();
                System.out.print(temp.value + ", ");

                // 规则1: 有 right 没left,肯定不行
                if(temp.left == null && temp.right != null) {
                    return false;
                }

                // 规则2: 有 left 没 right,那么之后的节点都必须没有孩子
                // 规则3: 没 left 没 right,那么之后的节点也都必须没有孩子
                // 那么总结就是只要没 right,之后的节点也都必须没有孩子
                if(temp.right == null){
                    shouldNoChild = true;
                }

                // 如果后面的节点必须是Leaf节点,那么假如还存在儿子,那肯定有问题
                if(shouldNoChild && (temp.left != null || temp.right !=null)){
                    return false;
                }
                if(temp.left != null){
                    queue.offer(temp.left);
                }
                if(temp.right != null){
                    queue.offer(temp.right);
                }
            }
        }
        System.out.println("isCompleteBinaryTree --- end ---");

        return true;

    }

2.7 判断一棵树是不是平衡二叉树 BBT

balance binary tree 平衡二叉树

平衡的意思就是别两边偏差太严重
1、 left 平衡
2、right 平衡
3、left 与 right的高度差小于等于 1

@NoArgsConstructor
    @AllArgsConstructor
    private static class BBTReturn {
        private boolean isBalance; // 是否平衡
        private int height; // 高度
    }

    /**
     * 平衡二叉树
     * 1、left 子树必须平衡
     * 2、right 子树必须平衡
     * 3、left 与 right子树的高度不超过1
     */
    private static boolean isBalanceBinaryTree(Node temp) {
        return processBalanceBinaryTree(temp).isBalance;
    }

    private static BBTReturn processBalanceBinaryTree(Node temp) {
        if (temp == null) {
            return new BBTReturn(true, 0);
        }
        BBTReturn bbtReturn = new BBTReturn();
        BBTReturn leftReturn = processBalanceBinaryTree(temp.left);
        BBTReturn rightReturn = processBalanceBinaryTree(temp.right);

        bbtReturn.height = Math.max(leftReturn.height, rightReturn.height) + 1;
        bbtReturn.isBalance = leftReturn.isBalance && rightReturn.isBalance && Math.abs(leftReturn.height - rightReturn.height) <= 1;

        return bbtReturn;
    }

2.8 判断一棵树是不是完全二叉树 FBT

    @AllArgsConstructor
    @NoArgsConstructor
    private static class FBTReturnData {
        int height; // 树的高度
        int nodeNums; // 节点的数量
    }

    private static boolean isFullBinaryTree(Node temp) {
        if (null == temp) {
            return true;
        }
        FBTReturnData returnData = processFBT(temp);
        // 满二叉树: 2 ^ 节点高度 - 1 = 节点数量
        return Math.pow(2 , returnData.height) - 1 == returnData.nodeNums;
    }

    private static FBTReturnData processFBT(Node temp) {
        if (temp == null) {
            return new FBTReturnData(0, 0);
        }
        FBTReturnData leftRD = processFBT(temp.left);
        FBTReturnData rightRD = processFBT(temp.right);
        // 节点数量
        int nodeNums = leftRD.nodeNums + rightRD.nodeNums + 1;
        int height = Math.max(leftRD.height, rightRD.height) + 1;
        return new FBTReturnData(height,nodeNums);
    }

2.9 找两个节点的最近共同祖先

LowestCommonAncestor 最进共同祖先

1、 使用 Map记录父亲链条

public static void testLowestCommonAncestor(){
        Node node1 = new Node(1);
        Node node2 = new Node(2);
        Node node3 = new Node(3);
        Node node4 = new Node(4);
        Node node5 = new Node(5);
        Node node6 = new Node(6);
        Node node7 = new Node(7);

        node1.left = node2;
        node1.right = node3;

        node2.left = node4;
        node2.right = node5;

        node3.left = node6;
        node3.right = null;

        node6.left = null;
        node6.right = node7;

        Node lowestCommonAncestor = getLowestCommonAncestor(node1, node7, node3);
        System.out.println("lowestCommonAncestor -> " + lowestCommonAncestor.value);
    }

    /**
     * 前提:node1 和 node2 必须在树里面
     * 假设每个 Node 的值都不相等
     */
    private static Node getLowestCommonAncestor(Node root, Node node1, Node node2) {
        if (root == null || node1 == null || node2 == null) {
            return null;
        }
        Map<Node, Node> fatherMap = new HashMap<>();
        fatherMap.put(root, null);

        processLCA(root, fatherMap);

        Set<Node> node1Fathers = new HashSet<>();
        Node node1Father = node1;
        while(node1Father != null){
            node1Fathers.add(node1Father);
            // 递归找爸爸
            node1Father = fatherMap.get(node1Father);
        }

        System.out.println("node1Fathers -> " + JSON.toJSONString(node1Fathers));
        Node node2Father = node2;

        while(node2Father != null){
            if(node1Fathers.contains(node2Father)){
                return node2Father;
            }
            // 递归找爸爸
            node2Father = fatherMap.get(node2Father);
        }
        return null;
    }

    private static void processLCA(Node temp, Map<Node, Node> fatherMap) {
        if (null == temp) {
            return;
        }
        if(temp.left != null){
            fatherMap.put(temp.left ,temp);
            processLCA(temp.left, fatherMap);

        }
        if(temp.right != null){
            fatherMap.put(temp.right ,temp);
            processLCA(temp.right, fatherMap);

        }
    }

2、 使用递归

看图,套着代码看下,好理解~

private static Node getLowestCommonAncestor2(Node head, Node node1, Node node2) {
        // 如果当前节点为空,直接返回 null
        if (null == head) {
            return head;
        }
        // 如果当前节点为其中一个目标节点,直接返回该节点
        if (head == node1) {
            return node1;
        }
        if (head == node2) {
            return node2;
        }

        Node left = getLowestCommonAncestor2(head.left, node1, node2);
        Node right = getLowestCommonAncestor2(head.right, node1, node2);

        // 比如叶子节点,如果左右子树返回的节点都为空,说明该节点不包含目标节点,返回 null
        if(left == null && right == null){
            return null;
        }

        // 如果左子树返回的节点不为空,右子树返回的节点为空,返回左子树的结果
        if(left != null && right == null){
            return left;
        }
        // 如果左子树返回的节点为空,右子树返回的节点不为空,返回右子树的结果
        if(left == null && right != null){
            return right;
        }

        // (这是上图的情况二)如果左右子树分别返回了目标节点,说明当前节点是最近的公共祖先节点
        return head;
    }

2.10 找某个节点的后继节点

假如 Node 有 Parent 指针

 private static void testSuccessorNode() {
        Node node1 = new Node(1);
        Node node2 = new Node(2);
        Node node3 = new Node(3);
        Node node4 = new Node(4);
        Node node5 = new Node(5);
        Node node6 = new Node(6);
        Node node7 = new Node(7);

        node1.left = node2;
        node1.right = node3;

        node2.left = node4;
        node2.right = node5;

        node3.left = node6;
        node3.right = node7;

        node1.parent = null;

        node2.parent = node1;
        node3.parent = node1;

        node4.parent = node2;
        node5.parent = node2;

        node6.parent = node3;
        node7.parent = node3;
        /*
                        1
                 2           3
             4      5     6     7
         */

        Node root = node1;
        getSuccessorNode(root, node5); // 5的下一个是1
        getSuccessorNode(root, node1); // 1的下一个是6
        getSuccessorNode(root, node7); // 7的下一个是null
        getSuccessorNode(root, node6); // 6的下一个是3

    }

    private static void getSuccessorNode(Node root, Node target) {
        if(root == null || target == null){
            return;
        }
        Node temp = target;
        // 规则1 :target 如果有 right 孩子,那么就一直往左下角走
        // 比如 Node1
        if(temp.right != null){
            temp = temp.right;
            while(temp.left != null){
                temp = temp.left;
            }
            System.out.println("target -> " + target.value + ", next -> " + temp.value);
            return;
        } else {
            // 规则2:target 如果没有 right,并且他最上面父亲是某个Node的left孩子,那么打印这个Node
            // 比如Node5
            Node parent = target.parent;
            while(parent != null && parent.right == temp){
                temp = parent;
                parent = temp.parent;
            }
            // 退出这个 while 循环,要么就是 parent到头了,到root了,要么就是 parent.left == temp
            // 属于2情况
            if(parent != null){
                System.out.println("target -> " + target.value + ", next -> " + parent.value);
                return;
            } else {
                // 规则3,target 如果没有 right,并且他最上面父亲是null,那么他就是最右下角的叶子节点
                // 比如Node7
                System.out.println("target -> " + target.value + ", next -> " + null);
            }
        }
    }

2.11 序列化和反序列化一颗二叉树

private static void testSerializeTree() {
        Node node1 = new Node(1);
        Node node2 = new Node(2);
        Node node3 = new Node(3);

        node1.left = null;
        node1.right = node2;

        node2.left = node3;
        node2.right = null;

        node3.left = null;
        node3.right = null;

        StringBuilder stringBuilder = new StringBuilder("");
        serializeTreePreOrder(node1, stringBuilder);
        System.out.println(stringBuilder.toString());

        Node head = unSerializeTree(stringBuilder.toString());
        System.out.println(head);
    }

    /**
     * 用前序遍历,中左右
     */
    private static void serializeTreePreOrder(Node temp, StringBuilder stringBuilder) {
        if (temp == null) {
            stringBuilder.append("#");
            stringBuilder.append(",");
            return;
        } else {
            stringBuilder.append(temp.value);
            stringBuilder.append(",");
            serializeTreePreOrder(temp.left, stringBuilder);
            serializeTreePreOrder(temp.right, stringBuilder);
            return;
        }
    }

    private static Node unSerializeTree(String str) {
        if(str == null || str.length() == 0){
            return null;
        }
        String[] strArr = str.split(",");
        LinkedList<String> queue = new LinkedList<>();
        for (int i = 0; i < strArr.length; i++) {
            queue.offer(strArr[i]);
        }
        return unSerializePreOrder(queue);

    }

    private static Node unSerializePreOrder(LinkedList<String> queue) {
        String value = queue.poll();
        if(value.equals("#")){
            return null;
        }
        // 1,#,2,3,#,#,#
        Node head = new Node(Integer.valueOf(value));
        head.left = unSerializePreOrder(queue);
        head.right = unSerializePreOrder(queue);

        return head;
    }

2.12 折叠一张纸,凹凸凹凸

自己拿出一张纸,折叠一次, 要么凹进去,要么吐出来,

折叠1次,折痕1凹进去,写1 凹 (一层,打印 1凹)

折叠2次,折痕1上面的2凹,下面的2凸 (二层,打印 2凹 1凹 2凸)

折叠3次,折痕2上面的3凹,下面的3凸 (三层,打印 3凹 2凹 3凸 1凹 3凹 2凸 3凸)

看右边的二叉树,就是中序遍历的过程。

规律就是,画成二叉树,往左是凹 down往右走是凸 up

private static void testPrintPaperFloding() {
        System.out.println();
        System.out.println("=== testPrintPaperFloding ===");
        int level = 3; // 层高
        printPaperFolding(level);
    }

    private static void printPaperFolding(int level) {
        if (level < 1) {
            return;
        }
        if (level == 1) {
            System.out.println("凹");
        }
        processPaperFolding(level, 1 ,true);
    }

    private static void processPaperFolding(int totalLevel, int currentLevel ,boolean down) {
        if (currentLevel > totalLevel) { // 比如当前是第4层,那么超出3层了,就不打印了
            return;
        }
        processPaperFolding(totalLevel, currentLevel + 1, true);
        if(down){
            System.out.print(currentLevel + "-" + "凹" + ",");
        } else {
            System.out.print(currentLevel + "-" + "凸" + ",");
        }
        processPaperFolding(totalLevel, currentLevel + 1, false);
    }

2.13 打印从右往左看到的节点

private static void printRight2Left(Node head) {
        System.out.println("=== printRight2Left ===");
        LinkedList<Node> queue = new LinkedList<>();
        queue.offer(head);

        while (!queue.isEmpty()){
            List<Node> levelNodeList = new LinkedList<>();
            int levelSize = queue.size();
            for (int i = 0; i < levelSize; i++) {
                Node temp = queue.poll();
                // 单纯的打印就是前序遍历
//                System.out.print(temp.value + ", ");
                if(temp.left != null){
                    queue.offer(temp.left);
                }
                if(temp.right != null){
                    queue.offer(temp.right);
                }
                levelNodeList.add(temp);
            }
            System.out.println(levelNodeList.get(levelNodeList.size() - 1).value);
        }
    }

2.14 求一棵树的直径

如上图

  • 如果需要经过 Root 节点,那么直径就是5
  • 如果可以不经过 Root节点,那直径就是6

LeetCode 上的极端 Case

1、假如需要经过 Root节点

这个比较简单,用递归判断左右子树的最长,然后相加就行了。

private static void testGetTreeDiameter() {
        System.out.println();
        System.out.println("=== testGetTreeDiameter ===");
        Node node1 = new Node(1);
        Node node2 = new Node(2);
        Node node3 = new Node(3);
        Node node4 = new Node(4);
        Node node5 = new Node(5);
        Node node6 = new Node(6);
        Node node7 = new Node(7);
        Node node8 = new Node(8);
        Node node9 = new Node(9);

        node1.left = node2;
        node1.right = node3;

        node2.left = node4;
        node2.right = node5;

        node4.left = node6;
        node6.left = node8;

        node5.right = node7;
        node7.right = node9;

        int diameter = getTreeDiameter(node1);
        System.out.println("must pass root -> " + diameter);
//        System.out.println(getTreeDiameterCanNoRoot(node1));

        DiameterReturnData diameterRD = getTreeDiameter2(node1);
        System.out.println(diameterRD.getDiameter());
        System.out.println(diameterRD.getTreeDiameter());

    }

private static int getTreeDiameter(Node root) {
        if (null == root) {
            return 0;
        }
        int leftDiameter = getLongDiameter(root.left);
        int rightDiameter = getLongDiameter(root.right);
        // root 的两边最长相加
        return leftDiameter + rightDiameter;
    }

    /**
     * 获取最长路径,要么就是左边的长,要么就是右边的长
     */
    private static int getLongDiameter(Node temp) {
        if (null == temp) {
            return 0;
        }
        int leftDiameter = getLongDiameter(temp.left);
        int rightDiameter = getLongDiameter(temp.right);
        int max = leftDiameter > rightDiameter ? leftDiameter : rightDiameter;
        return max + 1;
    }

2、可以不经过 Root 节点

思路一:每个节点都当成是 Root 节点,然后去求最大
/**
     * 可以不经过root节点
     * 宽度遍历的方法去遍历所有节点,然后求最大
     */
    private static int getTreeDiameterCanNoRoot(Node root) {
        LinkedList<Node> queue = new LinkedList<>();
        queue.offer(root);

        int max = -1;
        while (!queue.isEmpty()) {
            int queueSize = queue.size();
            for (int i = 0; i < queueSize; i++) {
                Node temp = queue.poll();
//                System.out.print(temp.value + ",");
                int diameter = getTreeDiameter(temp);
                max = max > diameter ? max : diameter;

                if (temp.left != null) {
                    queue.offer(temp.left);
                }
                if (temp.right != null) {
                    queue.offer(temp.right);
                }
            }
        }
        return max;
    }
思路二(强烈推荐):冗余数据一次求解

这种方法的返回值,

要经过 Root 节点,取 diameter  字段

不需要经过 Root 节点,取 treeDiameter  字段

private static DiameterReturnData getTreeDiameter2(Node root) {
        if (root == null) {
            // 相比 return DiameterReturnData(0,0,0,0) ,返回null,对我来说更容易处理
            return null;
        }
        DiameterReturnData leftRD = getTreeDiameter2(root.left);
        DiameterReturnData rightRD = getTreeDiameter2(root.right);
        int leftMax = 0;
        int rightMax = 0;
        int treeDiameter = 0;
        if (leftRD != null) {
            leftMax = Math.max(leftRD.getLeftMax(), leftRD.getRightMax()) + 1;
            treeDiameter = leftRD.getTreeDiameter();
        }
        if (rightRD != null) {
            rightMax = Math.max(rightRD.getLeftMax(), rightRD.getRightMax()) + 1;
            treeDiameter = Math.max(treeDiameter, rightRD.getTreeDiameter());

        }
        int diameter = leftMax + rightMax;
        treeDiameter = Math.max(treeDiameter, diameter);
        return new DiameterReturnData(leftMax, rightMax, diameter, treeDiameter);
    }


public static class DiameterReturnData {
        private int leftMax;
        private int rightMax;
        private int diameter;
        private int treeDiameter;

        public int getLeftMax() {
            return leftMax;
        }

        public void setLeftMax(int leftMax) {
            this.leftMax = leftMax;
        }

        public int getRightMax() {
            return rightMax;
        }

        public void setRightMax(int rightMax) {
            this.rightMax = rightMax;
        }

        public int getDiameter() {
            return diameter;
        }

        public void setDiameter(int diameter) {
            this.diameter = diameter;
        }

        public DiameterReturnData() {
        }

        public int getTreeDiameter() {
            return treeDiameter;
        }

        public void setTreeDiameter(int treeDiameter) {
            this.treeDiameter = treeDiameter;
        }

        public DiameterReturnData(int leftMax, int rightMax, int diameter, int treeDiameter) {
            this.leftMax = leftMax;
            this.rightMax = rightMax;
            this.diameter = diameter;
            this.treeDiameter = treeDiameter;
        }
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Python二叉树面试题有很多种,以下是其中几个常见的面试题: 1. 二叉树的最大深度:这个问题要求计算给定二叉树的最大深度。可以使用递归的方法来解决,递归函数的定义是返回当前节点的深度,递归终止条件是节点为空时返回0,递归过程中比较左右子树的深度并返回较大值加1。时间复杂度为O(n),空间复杂度为O(n)。 2. 二叉树的前序遍历:这个问题要求按照前序遍历的顺序输出二叉树的节点值。可以使用递归或迭代的方法来解决。递归方法的思路是先输出当前节点的值,然后递归遍历左子树,最后递归遍历右子树。迭代方法可以使用栈来辅助实现,把根节点压入栈中,然后循环弹出栈顶节点,输出其值,并将其右子节点和左子节点依次压入栈中。时间复杂度为O(n),空间复杂度为O(n)。 3. 二叉树的层序遍历:这个问题要求按照层序遍历的顺序输出二叉树的节点值。可以使用队列来辅助实现。首先将根节点入队,然后循环直到队列为空,每次从队列中取出一个节点,输出其值,并将其左右子节点依次入队。时间复杂度为O(n),空间复杂度为O(n)。 以上是几个常见的Python二叉树面试题的解法,根据具体的问题要求和输入条件选择合适的解法即可。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [【编程之路】面试必刷TOP101:二叉树系列(23-30,Python实现)](https://blog.csdn.net/be_racle/article/details/125531755)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [【面试题8】二叉树的下一个节点](https://download.csdn.net/download/weixin_38589168/14035034)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值