剑指offer-二叉树递归

1.重建二叉树:根据前序和中序的遍历结果,重构建二叉树并返回其根节点

题目链接:力扣

方法:递归

对于任意树,前序遍历的形式,根节点是前序遍历的第一个节点

【根节点,【左子树前序遍历结果】,【右子树的前序遍历结果】】

中序遍历的形式

【【左子树的中序遍历结果】,根节点,【右子树的中序遍历结果】】

只要我们在中序遍历中定位到根节点,那么我们就可以分别知道左子树和右子树中的节点数目。由于同一颗子树的前序遍历和中序遍历的长度显然是相同的,因此我们就可以对应到前序遍历的结果中,对上述形式中的所有左右括号进行定位

这样以来,我们就知道了左子树的前序遍历和中序遍历结果,以及右子树的前序遍历和中序遍历结果,我们就可以递归地对构造出左子树和右子树,再将这两颗子树接到根节点的左右位置。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    private Map<Integer, Integer> map;
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        int n = preorder.length;
        map = new HashMap<>();
        //将中序遍历结果数组加入map,key是数组值,value是数组的索引,方便查找前序遍历的子树的范围
        for(int i =0;i<n;i++){
            map.put(inorder[i],i);
        }
        TreeNode root =build(preorder,inorder,0,n-1,0,n-1);
        return root;
    }
    //构建二叉树,preorder_left这是代表索引位置
    public TreeNode build(int[] preorder, int[] inorder,int preorder_left,int preorder_right,int inorder_left,int inorder_right){
        //递归必须明确啥时候,跳出递归,开始出栈
        if(preorder_left>preorder_right) return null;
        //先建立头节点
        TreeNode node = new TreeNode(preorder[preorder_left]);
        //确定左节点的范围,和右节点范围,方便建立节点的左右节点
        //找到前序的头节点,对应中序的索引位置,确定左子树,右子树的范围
        int inorder_root= map.get(preorder[preorder_left]);
        //左子树和右子树的节点个数不同吧,两个不能共用len
        int len = inorder_root-inorder_left;
        // 先序遍历中「从 左边界+1 开始的 size_left_subtree」个元素就对应了中序遍历中「从 左边界 开始到 根节点定位-1」的元素
        node.left = build(preorder,inorder,preorder_left+1,preorder_left+len,inorder_left,inorder_root-1);
        node.right = build(preorder,inorder,preorder_left+len+1,preorder_right,inorder_root+1,inorder_right);
        return node;
    }
}

2.判断B是不是A的子树,这里是判断的B树的值是否跟A树相同

题目链接:力扣

输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)

B是A的子结构, 即 A中有出现和B相同的结构和节点值。

算法流程:

recur(A, B) 函数:

终止条件:
1当节点 B 为空:说明树 B 已匹配完成(越过叶子节点),因此返回true ;
2当节点 A为空:说明已经越过树 A 叶子节点,即匹配失败,返回 false ;
3当节点 A 和 B的值不同:说明匹配失败,返回false ;
返回值:
1判断 A和 B的左子节点是否相等,即 recur(A.left, B.left) ;
2判断 A 和 B 的右子节点是否相等,即 recur(A.right, B.right) ;
3isSubStructure(A, B) 函数:

特例处理: 当 树 A为空 或 树 B 为空 时,直接返回 false ;但是在写程序时A!=NULL,否则无法判断以后的与的判断;
返回值: 若树 B 是树 A 的子结构,则必满足以下三种情况之一,因此用或 || 连接;
1以 节点 A为根节点的子树 包含树 B ,对应 recur(A, B);
2树 B 是 树 A 左子树 的子结构,对应 isSubStructure(A.left, B);
3树 B是 树 A右子树 的子结构,对应 isSubStructure(A.right, B);
以上 2. 3. 实质上是在对树 A做 先序遍历 。

调试错误点:((A != null) && (B != null) )

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public boolean isSubStructure(TreeNode A, TreeNode B) {
        //1.前序遍历A树,这里AB != NULL;因为与操作,当为true时,才会判断后面的与操作
        return ((A != null) && (B != null) )&&(recur(A,B) || isSubStructure(A.left,B) || isSubStructure(A.right,B));
    }
    //判断以A为根节点,和以B为根节点,的树相不相同。
    boolean recur(TreeNode A,TreeNode B){
        //1.终止条件
        if(B == null) return true;//此时已经完成B树的匹配
        if(A == null || A.val != B.val) return false;//A树已经到了最后的叶子节点的最后,以A为根节点B为根节点的树,不相同
        return (recur(A.left,B.left) && recur(A.right,B.right));
    }
}

3.镜像二叉树

题目链接力扣

思路:

显然,我们从根节点开始,递归地对树进行遍历,并从叶子节点先开始翻转得到镜像。如果当前遍历到的节点 \textit{root}root 的左右两棵子树都已经翻转得到镜像,那么我们只需要交换两棵子树的位置,即可得到以 \textit{root}root 为根节点的整棵子树的镜像。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public TreeNode mirrorTree(TreeNode root) {
        if(root == null) return null;
        TreeNode left = mirrorTree(root.left);
        TreeNode right = mirrorTree(root.right);
        root.left = right;
        root.right = left;
        return root;
    }
}

4.二叉树中和为某一值的路径

题目链接:力扣

解题思路:本问题是二叉树方案搜索问题,使用回溯,包含先序遍历+路径记录

先序遍历过程中把节点加入数组中,当递归到链表的最后,再把链表中的值删除,最后回溯到,再遍历整棵左子树。最后回溯结束时,路径中的节点应该是为空的;

先序遍历: 按照 “根、左、右” 的顺序,遍历树的所有节点。
路径记录: 在先序遍历中,记录从根节点到当前节点的路径。当路径为 ① 根节点到叶节点形成的路径 且 ② 各节点值的和等于目标值 sum 时,将此路径加入结果列表。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    LinkedList<List<Integer>> res = new LinkedList<>();
    //定义用于储存路径的单链表
    //这里必须要写好了linkedlist类,应该时list没有这个方法
    LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> pathSum(TreeNode root, int target) {
        recur(root,target);
        return res;
    }
    void recur(TreeNode node,int target){
        //判回溯条件
        if(node == null) return;
        path.add(node.val);
        target -=node.val;
        //递归开始回溯的条件,当到达叶子节点时,target同时为0
        if(target == 0 && node.left ==null && node.right == null){
            //这里必须新建一个链表,否则path是再不断更新,最后的path都是空了
            res.add(new LinkedList(path)); 
        }
        //左子树递归
        recur(node.left,target);
        recur(node.right,target);
        //递归回溯时要一步步删除path中的值
        path.removeLast();
    }
}

5.二叉树的深度

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

题目链接:力扣

分析:利用后序遍历,判断左子树和右子树的深度

方法一:后序遍历(DFS)
树的后序遍历 / 深度优先搜索往往利用 递归 或 栈 实现,本文使用递归实现。
关键点: 此树的深度和其左(右)子树的深度之间的关系。显然,此树的深度 等于 左子树的深度 与 右子树的深度 中的 最大值 +1

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public int maxDepth(TreeNode root) {
        //递归结束条件
        if(root == null) return 0;
        //每调用一次递归加一个值
        return Math.max(maxDepth(root.left),maxDepth(root.right))+1;

    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值