剑指offer—树

题目代号: 剑指 Offer 07. 重建二叉树

题目描述:

输入某二叉树的前序遍历和中序遍历的结果,请构建该二叉树并返回其根节点。

假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

测试用例:

在这里插入图片描述

我的分析:

1、因为之后我们需要一直知道每个节点在中序遍历中的位置,所以要把这个节点放入map中
2、从前序遍历中拿出根节点
3、找到根节点在中序遍历中的位置
4、计算左子树的长度
5、建立树,左子树递归,右子树递归(注意括号里的范围,左边界包括右边界不包括)

代码:

public TreeNode buildTree(int[] preorder, int[] inorder) {
        //后面我们需要做一个事,就是不断的去中序遍历数组中找指定元素的下标,所以我把这个东西先存放进map中
        HashMap<Integer,Integer> map = new HashMap<>();
        for (int i = 0;i < inorder.length;i++){
            map.put(inorder[i],i);//键是这个元素,值是下标
        }

        return helper(preorder,0,preorder.length,inorder,0,inorder.length,map);
    }
    public TreeNode helper(int[] preorder,int pre_left,int pre_right,int[] inorder,int in_left,int in_right,HashMap<Integer,Integer> map){
        if(pre_left == pre_right) return null;
        //前序遍历的第一个肯定就是根节点了
        int root_val = preorder[pre_left];
        //根节点在中序遍历中的位置
        int in_root_idx = map.get(root_val);
        //左半子树的长度
        int leftNum = in_root_idx - in_left;

        //开始建树了哈,从头节点开始,然后左递归,右递归
        TreeNode root =new TreeNode(root_val);

        root.left = helper(preorder,pre_left+1,pre_left+1+leftNum,inorder,in_left,in_root_idx,map);
        root.right = helper(preorder,pre_left+1+leftNum,pre_right,inorder,in_root_idx+1,in_right,map);
        return root;
    }

题目代号: 剑指 Offer 26. 树的子结构

题目描述:

在这里插入图片描述

测试用例:

输入:A = [3,4,5,1,2], B = [4,1]
输出:true

我的分析:

在这里插入图片描述

代码:

public boolean isSubStructure(TreeNode A, TreeNode B) {
        //1、特例处理
        if(A == null || B == null) return false;//只要任意一棵树是空,那肯定不是子树了
        
        //2、返回值
        return helper(A,B) || isSubStructure(A.left,B) || isSubStructure(A.right,B);

    }
    public boolean helper(TreeNode root1,TreeNode root2){//判断以root1为节点的树1里面是否包含子树root2
        //1、终止条件
        if(root2 == null) return true;//说明子树2遍历完了,那就符合了
        if(root1 == null) return false;//树1遍历完了,子树2还有剩余,那完了

        //2、返回值
        if(root1.val == root2.val){//想符合的话,那就得左中右全相同
            return helper(root1.left,root2.left) && helper(root1.right,root2.right);
        }else {
            return false;
        }

    }

题目代号: 剑指 Offer 27. 二叉树的镜像

题目描述:

在这里插入图片描述

测试用例:

输入:root = [4,2,7,1,3,6,9]
输出:[4,7,2,9,6,3,1]

我的分析:

在这里插入图片描述
代码:

public TreeNode mirrorTree(TreeNode root) {
        if(root == null) return null;//如果这棵树是空,直接返回空就好了
        TreeNode tmp = root.left;//用节点tmp暂存左子树
        root.left = mirrorTree(root.right);
        root.right = mirrorTree(tmp);
        return root;
    }

题目代号: 剑指 Offer 28. 对称的二叉树

题目描述:

在这里插入图片描述
测试用例:

示例 1:

输入:root = [1,2,2,3,4,4,3]
输出:true
示例 2:

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

我的分析:

笔者一般习惯将判断两个子树是否相等或对称类型的题的解法叫做“四步法”:
(1)如果两个子树都为空指针,则它们相等或对称
(2)如果两个子树只有一个为空指针,则它们不相等或不对称
(3)如果两个子树根节点的值不相等,则它们不相等或不对称
(4)根据相等或对称要求,进行递归处理。

代码:

 public boolean isSymmetric(TreeNode root) {
        if(root == null){
            return true;
        }else {
            return check(root.left,root.right);
        }
    }

    public boolean check(TreeNode l,TreeNode r){
        if(l == null && r == null){
            return true;
        }else if(l == null || r == null){
            return false;
        }else if(l.val != r.val){
            return false;
        }else {
            return check(l.left,r.right) && check(l.right,r.left);
        }
    }

题目代号: 剑指 Offer 36. 二叉搜索树与双向链表

题目描述:

在这里插入图片描述
在这里插入图片描述

我的分析:

其实就是一个中序遍历的过程,因为对于二叉查找树而言,中序遍历是一个升序过程

先左子树——>头节点——>右子树

对于某个节点cur来说,我们需要看它的前驱节点是不是空
如果是空,那就把当前节点视为head(一旦找到head节点,head指针就不动了),继续把pre = cur向前进
如果不是空,那就互相指,指完向前进

最后出了递归,就head指向头节点,pre指向尾节点了
在这里插入图片描述

代码:

Node pre, head;
    public Node treeToDoublyList(Node root) {
        if(root == null) return null;//1、特例处理: 若节点root为空,则直接返回;
        dfs(root);//2、转化为双向链表: 调用 dfs(root) ;
        //3、构建循环链表: 中序遍历完成后,head 指向头节点,pre 指向尾节点,因此修改 head 和 pre 的双向节点引用即可;
        head.left = pre;
        pre.right = head;
        
        return head;
    }
    void dfs(Node cur) {
        //1、终止条件: 当节点 cur 为空,代表越过叶节点,直接返回;
        if(cur == null) return;
        //2、递归左子树,即 dfs(cur.left) ;
        dfs(cur.left);
        //3、构建链表:看当前节点
        if(pre != null) {//当 pre 不为空时: 修改双向节点引用,即 pre.right = cur , cur.left = pre ;
            pre.right = cur;
            cur.left = pre;
            pre = cur;//保存 cur : 更新 pre = cur ,即节点 cur 是后继节点的 pre ;
        } else {
            head = cur;//当 pre 为空时: 代表正在访问链表头节点,记为 head ;
            pre = cur;//保存 cur : 更新 pre = cur ,即节点 cur 是后继节点的 pre ;
        }

        //4、递归右子树,即 dfs(cur.right) ;
        dfs(cur.right);
    }

题目代号: 剑指 Offer 37. 序列化二叉树

题目描述:

请实现两个函数,分别用来序列化和反序列化二叉树。

你需要设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。

提示:输入输出格式与 LeetCode 目前使用的方式一致,详情请参阅 LeetCode 序列化二叉树的格式。你并非必须采取这种方式,你也可以采用其他的方法解决这个问题。

测试用例:

在这里插入图片描述

我的分析:

其实就是按前序遍历来进行遍历,利用递归的思想,先头节点,左子树,右子树这样来依次 遍历,在每个节点之间都添加逗号就可以了。这样就序列化了

反序列化:把逗号依次剔除即可,构建成一颗树

为什么把构建树写成一个函数呢,就是为了递归

代码:

/**
     * 最近子问题
     * 当前树的序列化=join(root.val,左子树的序列化,右子树的序列化)
     */
    public String serialize(TreeNode root) {
        if (root == null) return "x";//空节点编码为x
        String left = serialize(root.left);
        String right = serialize(root.right);
        return String.join(",", String.valueOf(root.val), left, right);//表示用逗号来连接头节点,左子节点,右子节点
    }


    public TreeNode deserialize(String data) {
        LinkedList<String> queue = new LinkedList<>();
        for (String s : data.split(",")){
            queue.add(s);    
        } 
        return buildTree(queue);
    }

    /**
     * 最近子问题
     * 构建当前树=(root.val,构建左子树,构建右子树)
     */
    private TreeNode buildTree(LinkedList<String> queue) {
        String root = queue.removeFirst();
        if ("x".equals(root)) return null;//空节点为x
        return new TreeNode(Integer.parseInt(root), buildTree(queue), buildTree(queue));
    }

题目代号: 剑指 Offer 32 - I. 从上到下打印二叉树

题目描述:

剑指 Offer 32 - I. 从上到下打印二叉树

测试用例:

在这里插入图片描述

我的分析:

在这里插入图片描述
代码:

public int[] levelOrder(TreeNode root) {
        if(root == null) return new int[0];
        Queue<TreeNode> queue = new LinkedList<>(){{ add(root); }};
        ArrayList<Integer> ans = new ArrayList<>();
        while(!queue.isEmpty()) {
            TreeNode node = queue.poll();
            ans.add(node.val);
            if(node.left != null) queue.add(node.left);
            if(node.right != null) queue.add(node.right);
        }
        int[] res = new int[ans.size()];
        for(int i = 0; i < ans.size(); i++)
            res[i] = ans.get(i);
        return res;
    }

题目代号: 剑指 Offer 32 - II. 从上到下打印二叉树 II

题目描述:

从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。

测试用例:

在这里插入图片描述

我的分析:

先把头节点放入队列,list要分里外的

只要队列不为空,那就创建内list
遍历队列,将队列里的元素弹出,加入内list,只要队列的左右子树不为空,添加进入队列

把内list添加进入外list

代码:

 public List<List<Integer>> levelOrder(TreeNode root) {
        Queue<TreeNode> queue = new LinkedList<>();
        List<List<Integer>> res = new ArrayList<>();
        if (root != null) queue.add(root);
        while (!queue.isEmpty()){
            List<Integer> temp = new ArrayList<>();
            for(int i = queue.size(); i > 0; i--){//不能叠加的原因是会让queue增多
                TreeNode node = queue.poll();
                temp.add(node.val);
                if(node.left != null) queue.add(node.left);
                if(node.right != null) queue.add(node.right);
            }
            res.add(temp);
        }
        return res;

    }

题目代号: 剑指 Offer 32 - III. 从上到下打印二叉树 III

题目描述:

请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。

测试用例:

在这里插入图片描述

我的分析:

有两点需要注意的:
这次创建的队列是双端队列,这样可以从头部添加元素,也可从尾部
要用结果res的长度,知道现在到底遍历到奇数层还是偶数层

把头节点加入队列中
只要队列不为空,那就遍历队列
创建内list,遍历队列,弹出队列元素,将队列元素添加入内list中(要分奇数层还是偶数层)
看左右子节点是否为空,然后加入队列中
在这里插入图片描述

代码:

public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> res = new ArrayList<>();//外列表

        Queue<TreeNode> queue = new LinkedList<>();
        if(root != null) queue.add(root);
        while (!queue.isEmpty()){
            LinkedList<Integer> temp = new LinkedList<>();//内列表
            for(int i = queue.size();i > 0;i--){
                TreeNode node = queue.poll();
                if(res.size() % 2 == 0) temp.addLast(node.val);// 偶数层 -> 队列尾部
                else temp.addFirst(node.val);//很巧妙,拿目前结果队列长度来判断现在是奇数层还是偶数层
                if(node.left != null) queue.add(node.left);
                if(node.right != null) queue.add(node.right);
            }
            res.add(temp);
        }
        return res;


    }

题目代号: 剑指 Offer 33. 二叉搜索树的后序遍历序列

题目描述:

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true,否则返回 false。假设输入的数组的任意两个数字都互不相同。

测试用例:

在这里插入图片描述

我的分析:

在这里插入图片描述

代码:

public boolean verifyPostorder(int[] postorder) {
        return cur(postorder, 0, postorder.length - 1);
    }

    public boolean cur(int[] postorder,int i,int j){
        if(i >= j) return true;//说明已经结束了
        int t = i;
        while (postorder[t] < postorder[j]) t++;
        int m = t;//划分成[i,m-1][m,j-1][j]三部分
        while (postorder[t] > postorder[j]) t++;//左半部分已经符合了,那就只看右半部分就可以了
        return t == j && cur(postorder,i,m-1) && cur(postorder,m,j-1);
    }

题目代号: 剑指 Offer 34. 二叉树中和为某一值的路径

题目描述:

输入一棵二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。

测试用例:

在这里插入图片描述

我的分析:

要注意,这个题要求从头节点开始一直到叶子节点结束

1、先看这个节点
说白了就是只要这个节点不是空,那就增加进入链表中,那目标值也就下降了,
最后找到一条链表链路的条件是 左子树为空,右子树为空,总和为0;因为要到叶子节点

2、看左子树,右子树
左递归,右递归

3、回溯
把当前尾节点扔出去
代码:

List<List<Integer>> res = new LinkedList<>();
    LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> pathSum(TreeNode root, int target) {

        recur(root,target);
        return res;
    }
    public void recur(TreeNode root,int target){
        if(root == null) return;
        //1、先看这个节点
        path.add(root.val);
        target -= root.val;
        if(root.left == null && root.right == null && target == 0){
            res.add(new LinkedList<Integer>(path));
        }
        //2、看左右子树
        recur(root.left,target);
        recur(root.right,target);
        //3、回溯另一条路
        path.removeLast();

    }

题目代号: 剑指 Offer 55 - I. 二叉树的深度

题目描述:

输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。

测试用例:

在这里插入图片描述

我的分析:

其实就是玩的递归,就是看左子树和右子树里面的最大高度,然后加上头节点即可

代码:

public int maxDepth(TreeNode root) {
        if(root == null){
            return 0;
        }else{
            int left = maxDepth(root.left);
            int right = maxDepth(root.right);
            return Math.max(left,right)+1;
        }
    }

题目代号: 剑指 Offer 54. 二叉搜索树的第k大节点

题目描述:

给定一棵二叉搜索树,请找出其中第k大的节点。

测试用例:

在这里插入图片描述
我的分析:

因为是从大到小,所以咱们遍历的时候,先右子树,后根节点,再左子树
在这里插入图片描述

在这里插入图片描述

代码:

int res,k;//记录最后结果
    public int kthLargest(TreeNode root, int k) {
        this.k = k;
        dfs(root);
        return res;
    }

    private void dfs(TreeNode root){
        if(root == null) {
            return;
        }
        dfs(root.right);
        //对于每个节点的判断就是这样,先给它统计减1,看它符合不符合,不符合它会自动递归的,放心吧
        k--;
        if(k == 0){
            res = root.val;
            return;
        }
        
        dfs(root.left);
        
    }

题目代号: 剑指 Offer 55 - II. 平衡二叉树

题目描述:

输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。

测试用例:

在这里插入图片描述

我的分析:

在这里插入图片描述
先判断这个根节点,再判断左子树和右子树

代码:

public boolean isBalanced(TreeNode root) {
        if (root == null) return true;
        return Math.abs(depth(root.left) - depth(root.right)) <= 1 && isBalanced(root.left) && isBalanced(root.right);
    }

    private int depth(TreeNode root) {
        if (root == null) return 0;
        return Math.max(depth(root.left), depth(root.right)) + 1;
    }

题目代号: 剑指 Offer 68 - I. 二叉搜索树的最近公共祖先

题目描述:

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

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

测试用例:

在这里插入图片描述

我的分析:

当p q都比根节点小的时候,那结果肯定位于左半子树
当p q都比根节点大的时候,那结果肯定位于右半子树
其他情况,那结果就是根节点了

代码:

if(root.val < p.val && root.val < q.val){//两个数都比根节点大,就递归右半子树
            return lowestCommonAncestor(root.right, p, q);
        }
            
        if(root.val > p.val && root.val > q.val){//两个数都比根节点小,就递归左半子树
            return lowestCommonAncestor(root.left, p, q);
        }
            
        return root;//否则返回根节点

题目代号: 剑指 Offer 68 - II. 二叉树的最近公共祖先

题目描述:

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

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

测试用例:

在这里插入图片描述

我的分析:

这个题与上一个题最大的不同就是二叉树,上一个题是二叉搜索树,那是有规律的,左边<根节点<右边,但二叉树是没有规律的

那我们就这样找,看p q是不是等于root,只要等于,那简单了,结果肯定就是root了

那接下来就三种情况:
p q位于两侧
p q都位于左侧(先找到哪个就是哪个)
p q都位于右侧(先找到哪个就是哪个)

代码:

public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {

        if(root == null) return null;//空树就不用找了
        if(root == p || root == q) return root;//如果p q其中一个节点是根节点,那祖先就是根节点
        /*
        就三种情况:
        p q位于两侧
        p q都位于左侧
        p q都位于右侧
         */
        TreeNode left = lowestCommonAncestor(root.left,p,q);//递归左子树,先找到谁,就是谁了
        TreeNode right = lowestCommonAncestor(root.right,p,q);
        if(right == null){
            return left;//右侧为空,p q都位于左侧
        }else if(left == null){
            return right;//左侧为空,p q都位于右侧
        }else {
            return root;//p q位于两侧
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值