二叉树的做题技巧 - 初级

二叉树的做题技巧 - 初级

简介

本文主要以代码说明为主,若有不理解,或有错误地方可留言指出说明,感谢^_^

更多代码: code

二叉树的基本概念

二叉树是一种非线性的数据结构,它由节点组成,每个节点最多有两个子节点,通常称为左子节点和右子节点。二叉树的特点是每个节点的子节点数量不超过2,且子节点的位置是固定的,即左子节点在右子节点之前。二叉树可以是空的,也可以包含一个或多个节点。在二叉树中,根节点位于最高层级,没有父节点,而其他所有节点都至少有一个父节点

二叉树的特殊类型

  • 满二叉树:每一层的节点数都达到最大值,即除了最后一层外,每层都完全填满,且所有节点都尽可能地向左对齐
  • 完全二叉树:除了最后一层外,其他各层的节点数都达到最大值,且最后一层的节点都连续集中在左侧。满二叉树是完全二叉树的一种特例
  • 平衡二叉树:也称为 A V L AVL AVL树,是一种特殊的二叉搜索树。它的特点是任何节点的两个子树的高度差的绝对值不超过1,并且左子树和右子树也都是平衡二叉树
  • 搜索二叉树:如果树非空,则树的根节点的值要么是小于其左子树中所有节点的值,要么是大于其右子树中所有节点的值;树的左子树和右子树本身也必须是二叉搜索树

二叉树的性质

  • 对于非空二叉树,第i层最多有 2 i − 1 2^{i−1} 2i1个节点。

  • 深度为h的二叉树最多有 2 h − 1 2^h−1 2h1个节点。

  • 具有n个节点的满二叉树的高度 h = l o g ⁡ ( n + 1 ) h=log⁡(n+1) h=log(n+1)(以2为底)。

  • 在完全二叉树中,如果按照从上至下从左至右的数组顺序对所有节点进行编号,则第i个节点的双亲节点编号为 ⌊ ( i − 1 ) / 2 ⌋ ⌊(i−1)/2⌋ ⌊(i1)/2,左孩子节点编号为 2 i + 1 2i+1 2i+1 ,右孩子节点编号为 2 i + 2 2i+2 2i+2

二叉树的遍历

  • 前序遍历:先访问根节点,再遍历左子树,最后遍历右子树。

  • 中序遍历:先遍历左子树,再访问根节点,最后遍历右子树。在二叉搜索树中,中序遍历会得到一个升序列。

  • 后序遍历:先遍历左子树和右子树,最后访问根节点。

  • 层次遍历:按照树的层次从上到下、从左到右进行遍历,通常使用队列来辅助实现

二叉树的特殊类型

满二叉树与完全二叉树

package com.heyqing;

import java.util.LinkedList;
import java.util.Objects;
import java.util.Queue;

/**
 * ClassName:CompleteBinaryTree
 * Package:com.heyqing
 * Description:
 *
 * @Date:2024/10/23
 * @Author:Heyqing
 */
public class CompleteBinaryTree {

    /**
     * 判断完全二叉树
     *
     * @param root
     * @return
     */
    public static boolean judgeBCT(TreeNode root) {
        /*
        使用层次遍历
        碰到第一个无右树的节点后的所有节点必须为叶子节点,否则就不是完全二叉树
         */
        if (Objects.isNull(root)) return false;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        boolean noRight = false;
        while (!queue.isEmpty()) {
            int loop = queue.size();
            for (int i = 0; i < loop; i++) {
                TreeNode first = queue.poll();
                /*
                碰到第一个无右树节点后,发现有不是叶子节点的节点 - false
                 */
                if (noRight && (Objects.nonNull(first.left) || Objects.nonNull(first.right))) {
                    return false;
                }
                /*
                有右没左 - false
                 */
                if (Objects.isNull(first.left) && Objects.nonNull(first.right)) {
                    return false;
                }
                if (Objects.nonNull(first.left)) {
                    queue.offer(first.left);
                }
                if (Objects.nonNull(first.right)) {
                    queue.offer(first.right);
                } else {
                    /*
                    第一个无右树的节点
                     */
                    noRight = true;
                }
            }
        }
        return true;
    }
}

平衡二叉树

package com.heyqing;

import java.util.Objects;

/**
 * ClassName:BalancedBinaryTree
 * Package:com.heyqing
 * Description:
 * 平衡二叉树
 *
 * @Date:2024/10/24
 * @Author:Heyqing
 */
public class BalancedBinaryTree {
    /*
    平衡二叉树 : | left.height - right.height | <= 1
     */


    /**
     * 平衡二叉树判断返回类
     */
    private static class BalanceInfo {
        /**
         * 是否为平衡二叉树
         */
        private boolean isBalance;

        /**
         * 树高
         */
        private int height;

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

    /**
     * 判断平衡二叉树
     *
     * @param root
     * @return
     */
    public static boolean isBBT(TreeNode root) {
        return achieveFuncByBalance(root).isBalance;
    }

    /**
     * 是否为平衡二叉树 - 实现函数
     *
     * @param root
     * @return
     */
    private static BalanceInfo achieveFuncByBalance(TreeNode root) {
        /*
        是否为平衡二叉树需要满足:左子树和右子树为平衡二叉树,自己的左右子树高度差小于等于 1
         */
        // 触底返回 - 叶子节点是平衡二叉树
        if (Objects.isNull(root)) return new BalanceInfo(true, 0);
        // 获取当前树的左子树
        BalanceInfo left = achieveFuncByBalance(root.left);
        // 获取当前树的右子树
        BalanceInfo right = achieveFuncByBalance(root.right);
        // 当前树高等于 :子树最大高度 + 1
        int height = Math.max(left.height, right.height) + 1;
        // 当前树是不是平衡二叉树 :左子树是平衡二叉树 and 右子树是平衡二叉树 and 自己的左右子树高度差小于 1
        boolean isBalance = left.isBalance && right.isBalance && Math.abs(left.height - right.height) <= 1;
        // 将当前树返回至顶层
        return new BalanceInfo(isBalance, height);
    }


}

搜索二叉树

package com.heyqing;

import java.util.Objects;

/**
 * ClassName:SearchBinaryTree
 * Package:com.heyqing
 * Description:
 * 搜索二叉树
 *
 * @Date:2024/10/22
 * @Author:Heyqing
 */
public class SearchBinaryTree {

    /**
     * 判断是否为搜索二叉树
     *
     * @param root
     * @return
     */
    public static boolean judgeBST(TreeNode root) {
        /*
        搜索二叉树:左子树都比自己小,右子树都比自己大
        使用中序遍历 LDR
        左树为搜索二叉树,右树为搜索二叉树
        左树的最大值小于 当前树
        右树的最小数大于 当前树
         */
        return isValidBST(root, Long.MIN_VALUE, Long.MAX_VALUE);
    }

    private static boolean isValidBST(TreeNode node, long minValue, long maxValue) {
        if (Objects.isNull(node)) {
            return true;
        }
        /*
        小于最小值或大于最大值 - 不是搜索二叉树
         */
        if (node.val <= minValue || node.val >= maxValue) {
            return false;
        }
        return isValidBST(node.left, minValue, node.val) && isValidBST(node.right, node.val, maxValue);
    }
}

二叉树的遍历

前序遍历

package com.heyqing;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Stack;

/**
 * ClassName:PreOrder
 * Package:com.heyqing
 * Description:
 * 先序遍历 :DLR
 *
 * @Date:2024/10/21
 * @Author:Heyqing
 */
public class PreOrder {


    /**
     * 先序遍历 - 递归
     *
     * @param root
     * @return
     */
    public static List<Integer> preOrderByRecursion(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        return achieveFuncByPOR(root, result);
    }

    /**
     * 实现函数 - 先序遍历 - 递归
     *
     * @param root
     * @param result
     * @return
     */
    private static List<Integer> achieveFuncByPOR(TreeNode root, List<Integer> result) {
        if (Objects.isNull(root)) return result;
        result.add(root.val);
        achieveFuncByPOR(root.left, result);
        achieveFuncByPOR(root.right, result);
        return result;
    }

    /**
     * 先序遍历 - 非递归
     *
     * @param root
     * @return
     */
    public static List<Integer> inOrderByNonRecursion(TreeNode root) {
        /*
        定义栈先将头压入
        然后弹出,获得栈顶元素进行操作,弹出后将孩子节点压入栈中(先右再左)
         */
        if (Objects.isNull(root)) return null;
        List<Integer> result = new ArrayList<>();
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root);
        while (!stack.isEmpty()) {
            TreeNode top = stack.pop();
            result.add(top.val);
            //先右
            if (Objects.nonNull(top.right)) {
                stack.push(top.right);
            }
            //后左
            if (Objects.nonNull(top.left)) {
                stack.push(top.left);
            }
        }
        return result;
    }
}

中序遍历

package com.heyqing;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Stack;

/**
 * ClassName:InOrder
 * Package:com.heyqing
 * Description:
 * 中序遍历:LDR
 *
 * @Date:2024/10/21
 * @Author:Heyqing
 */
public class InOrder {

    /**
     * 中序遍历 - 递归
     *
     * @param root
     * @return
     */
    public static List<Integer> inOrderByRecursion(TreeNode root) {
        List<Integer> listLDR = new ArrayList<>();
        return achieveFuncByIOR(root, listLDR);
    }

    /**
     * 中序遍历递归 - 实现函数
     *
     * @param root
     * @param listLDR
     * @return
     */
    private static List<Integer> achieveFuncByIOR(TreeNode root, List<Integer> listLDR) {
        if (Objects.isNull(root)) return listLDR;
        achieveFuncByIOR(root.left, listLDR);
        listLDR.add(root.val);
        achieveFuncByIOR(root.right, listLDR);
        return listLDR;
    }

    /**
     * 中序遍历 - 非递归
     *
     * @param root
     * @return
     */
    public static List<Integer> inOrderByNonRecursion(TreeNode root) {
        /*
        使用栈
        先将左边界压栈
        然后栈中一个 操作 再压入有孩子
         */
        List<Integer> listLDR = new ArrayList<>();
        Stack<TreeNode> stack = new Stack<>();
        pushAllLeftNode(stack, root);
        while (!stack.isEmpty()) {
            TreeNode top = stack.pop();
            if (Objects.nonNull(top)) {
                listLDR.add(top.val);
                pushAllLeftNode(stack, top.right);
            }
        }
        return listLDR;
    }

    /**
     * 将所有左边界压入栈中
     *
     * @param stack
     * @param root
     */
    private static void pushAllLeftNode(Stack<TreeNode> stack, TreeNode root) {
        while (Objects.nonNull(root)) {
            stack.push(root);
            root = root.left;
        }
    }
}

后序遍历

package com.heyqing;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Stack;

/**
 * ClassName:PostOrder
 * Package:com.heyqing
 * Description:
 * 后序遍历:LRD
 *
 * @Date:2024/10/21
 * @Author:Heyqing
 */
public class PostOrder {

    /**
     * 后序遍历 - 递归
     *
     * @param root
     * @return
     */
    public static List<Integer> postOrderByRecursion(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        return achieveFuncByPoOR(root, result);
    }

    /**
     * 实现函数 - 后序遍历 - 递归
     *
     * @param root
     * @param result
     */
    private static List<Integer> achieveFuncByPoOR(TreeNode root, List<Integer> result) {
        if (Objects.isNull(root)) return result;
        achieveFuncByPoOR(root.left, result);
        achieveFuncByPoOR(root.right, result);
        result.add(root.val);
        return result;
    }

    /**
     * 后序遍历 - 非递归
     *
     * @param root
     * @return
     */
    public static List<Integer> postOrderByNonRecursion(TreeNode root) {
        /*
        申请两个栈
        先将root压入栈中
        再将stack1弹出top1,向stack1压入top1的孩子(向左后右),将top1压入stack2,直至stack1 为空
        依次弹出stack2
         */
        List<Integer> result = new ArrayList<>();
        Stack<TreeNode> stack1 = new Stack<>();
        Stack<TreeNode> stack2 = new Stack<>();
        stack1.push(root);
        while (!stack1.isEmpty()) {
            TreeNode top1 = stack1.pop();
            if (Objects.nonNull(top1.left)) {
                stack1.push(top1.left);
            }
            if (Objects.nonNull(top1.right)) {
                stack1.push(top1.right);
            }
            stack2.push(top1);
        }
        while (!stack2.isEmpty()) {
            result.add(stack2.pop().val);
        }
        return result;
    }
}

层次遍历

package com.heyqing;

import java.util.*;

/**
 * ClassName:LevelOrder
 * Package:com.heyqing
 * Description:
 * 层次遍历
 *
 * @Date:2024/10/21
 * @Author:Heyqing
 */
public class LevelOrder {

    /**
     * 层次遍历
     *
     * @param root
     * @return
     */
    public static List<Integer> levelOrder(TreeNode root) {
        if (Objects.isNull(root)) return null;
        List<Integer> result = new ArrayList<>();
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            TreeNode first = queue.poll();
            result.add(first.val);
            if (Objects.nonNull(first.left)) {
                queue.offer(first.left);
            }
            if (Objects.nonNull(first.right)) {
                queue.offer(first.right);
            }
        }
        return result;
    }


    /**
     * 层次遍历 - 分层
     *
     * @param root
     * @return
     */
    public static List<List<Integer>> levelOrderLayer(TreeNode root) {
        if (Objects.isNull(root)) return null;
        List<List<Integer>> result = new ArrayList<>();
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            int loop = queue.size();
            List<Integer> temp = new ArrayList<>(loop);
            for (int i = 0; i < loop; i++) {
                TreeNode first = queue.poll();
                temp.add(first.val);
                if (Objects.nonNull(first.left)) {
                    queue.offer(first.left);
                }
                if (Objects.nonNull(first.right)) {
                    queue.offer(first.right);
                }
            }
            result.add(temp);
        }
        return result;
    }
}

二叉树的简单题练习

最低公共祖先

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。

百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”

纸条折痕

请把一段纸条竖着放在桌子上,然后从纸条的下边向上方对折1次,压出折痕后展开。此时折痕是凹下去的(下折痕),即折痕突起的方向指向纸条的背面。如果从纸条的下边向上方连续对折2次,压出折痕后展开,此时有三条折痕,从上到下依次是下折痕、下折痕和上折痕。

给定一个输入参数N,代表纸条从下边向上方连续对折N次。请从上到下打印所有折痕的方向。

答案

package com.heyqing;

import java.util.*;

/**
 * ClassName:Question
 * Package:com.heyqing
 * Description:
 *
 * @Date:2024/10/23
 * @Author:Heyqing
 */
public class Question {


    /**
     * 最低公共祖先 - The Lowest Common Ancestor
     *
     * @param root
     * @param node1
     * @param node2
     * @param version
     * @return
     */
    public static List<Integer> lca(TreeNode root, TreeNode node1, TreeNode node2, String version) {
        /*
        val1和val2一定在树上
        version1:使用map,记录每个节点的父节点,
                然后将node1一致回溯到头节点,再将node2回溯到头,
                在node2回溯的过程中set添加失败节点即为共同祖先
        version2: 纯算法 *_*
         */
        // 返回 node.hashcode 和 value
        List<Integer> result = new ArrayList<>(2);
        if (version.equals("v1")) return lcaV1(root, node1, node2);
        if (version.equals("v2")) return lcaV2(root, node1, node2);
        return result;
    }

    /**
     * 纸条折痕
     *
     * @param n
     * @return
     */
    public static List<String> PaperFold(int n) {
        /*
        将一个纸条面向自己对折,从上到下出现一个折痕,这个折痕是向外凸向里凹的,称之为凹折痕,反之则为凸折痕
        问一个纸条在理想条件下这样往复对折 n 次 ,从上到下的折痕依次是什么折痕
         */
        List<String> fold = new ArrayList<>(((1 << n) - 1));
        achieveFuncByPaperFold(1, n, true, fold);
        return fold;
    }


    /******************************************************private******************************************************/

    /**
     * 纸条折痕 - 实现函数
     *
     * @param i
     * @param n
     * @param down
     * @param fold
     */
    private static void achieveFuncByPaperFold(int i, int n, boolean down, List<String> fold) {
        /*
        对折 n 次后,产生的折痕是以 凹折痕为root节点其所有左子树都是以凹折痕这位root,右子树为凸折痕为root
        中序遍历即可得到从上到下的折痕顺序
         */
        if (i > n) return;
        achieveFuncByPaperFold(i + 1, n, true, fold);
        fold.add(down ? "down" : "up");
        achieveFuncByPaperFold(i + 1, n, false, fold);
    }

    /**
     * 最低公共祖先 - v1 - map
     *
     * @param root
     * @param node1
     * @param node2
     * @return
     */
    private static List<Integer> lcaV1(TreeNode root, TreeNode node1, TreeNode node2) {
        /*
        使用map,记录每个节点的父节点,
        然后将node1一致回溯到头节点,再将node2回溯到头,
        在node2回溯的过程中set添加失败节点即为共同祖先
         */
        List<Integer> result = new ArrayList<>(2);
        Map<TreeNode, TreeNode> fatherMap = new HashMap<>();
        achieveFuncByFullFatherMap(root, fatherMap);
        Set<TreeNode> node1FatherSet = new HashSet<>();
        TreeNode cur = node1;
        while (!Objects.equals(cur, fatherMap.get(cur))) {
            node1FatherSet.add(cur);
            cur = fatherMap.get(cur);
        }
        // 将头节点加入
        node1FatherSet.add(root);
        cur = node2;
        while (!Objects.equals(cur, fatherMap.get(cur))) {
            TreeNode father = fatherMap.get(cur);
            if (node1FatherSet.contains(father)) {
                //此时的father为最低公共祖先
                result.add(father.hashCode());
                result.add(father.val);
            }
        }
        return result;
    }

    /**
     * 最低公共祖先 - v2
     *
     * @param root
     * @param node1
     * @param node2
     * @return
     */
    private static List<Integer> lcaV2(TreeNode root, TreeNode node1, TreeNode node2) {
        List<Integer> result = new ArrayList<>(2);
        TreeNode father = achieveFuncByLcaV2(root, node1, node2);
        result.add(father.hashCode());
        result.add(father.val);
        return result;
    }

    /**
     * 最低公共祖先 - v2 - 实现函数
     *
     * @param root
     * @param node1
     * @param node2
     * @return
     */
    private static TreeNode achieveFuncByLcaV2(TreeNode root, TreeNode node1, TreeNode node2) {
        /*
        root 可能出现的情况
        node1,node2一个是另一个的最低公共祖先,特点就是与node1,node2的兄弟树必没有匹配值 : 先碰到谁谁就是最低公共祖先
        node1,node2一个不是另一个的最低公共祖先,特特点是他们分别在兄弟树上,必汇聚至一点 :第一个左右子树都右返回值的点
         */
        // 碰到 null 返回 null,碰到 node1 返回 node1 ,碰到 node2 返回 node2
        if (Objects.isNull(root) || Objects.equals(root, node1) || Objects.equals(root, node2)) {
            return root;
        }
        // left,right 就四种情况 null , node1 , node2 ,father
        TreeNode left = achieveFuncByLcaV2(root.left, node1, node2);
        TreeNode right = achieveFuncByLcaV2(root.right, node1, node2);
        // 这里为第四中情况,最多只会出现一次,即当前树的左子树与右子树分别有node1和node2
        // 出现过一次以后必不会再出现,因为node1,node2都在当前树下,兄弟节点返回的必为null
        if (Objects.nonNull(left) && Objects.nonNull(right)) {
            return root;
        }
        // 有两种种情况会走到这 : 1、node1,node2一个是另一个的最低公共祖先,2、第四中情况出现过一次后
        // 归根结底是因为两个节点都在当前树下,兄弟节点必为 null
        return Objects.nonNull(left) ? left : right;
    }

    /**
     * 填充父map
     *
     * @param root
     * @param fatherMap
     */
    private static void achieveFuncByFullFatherMap(TreeNode root, Map<TreeNode, TreeNode> fatherMap) {
        if (Objects.isNull(root)) return;
        fatherMap.put(root.left, root);
        fatherMap.put(root.right, root);
        achieveFuncByFullFatherMap(root.left, fatherMap);
        achieveFuncByFullFatherMap(root.right, fatherMap);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值