zoukankan      html  css  js  c++  java
  • LeetCode剑指offer二叉树系列

    LeetCode剑指offer二叉树系列

    07 重建二叉树

    题目

    输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
    例如,给出
    前序遍历 preorder = [3,9,20,15,7]
    中序遍历 inorder = [9,3,15,20,7]
    返回如下的二叉树:
    
        3
       / 
      9  20
        /  
       15   7
    
    来源:力扣(LeetCode)
    链接:https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof
    著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
    

    解题思路:

    前序遍历的性质:节点按照【根节点|左子树|右子树】排序

    中序遍历的性质:节点按照【左子树|根节点|右子树】排序

    以题目示例为例:

    前序遍历划分 [ 3 | 9 | 20 15 7 ]
    中序遍历划分 [ 9 | 3 | 15 20 7 ]
    根据以上性质,可得出以下推论:

    前序遍历的首元素 为 树的根节点 node 的值。
    在中序遍历中搜索根节点 node 的索引 ,可将 中序遍历 划分为 [ 左子树 | 根节点 | 右子树 ] 。
    根据中序遍历中的左 / 右子树的节点数量,可将 前序遍历 划分为 [ 根节点 | 左子树 | 右子树 ] 。

    以上子树的递推性质是 分治算法 的体现,考虑通过递归对所有子树进行划分。

    分治算法解析:
    递推参数: 根节点在前序遍历的索引 root 、子树在中序遍历的左边界 left 、子树在中序遍历的右边界 right ;

    终止条件: 当 left > right ,代表已经越过叶节点,此时返回 nullnull ;

    递推工作:

    建立根节点 node : 节点值为 preorder[root] ;
    划分左右子树: 查找根节点在中序遍历 inorder 中的索引 i ;
    为了提升效率,本文使用哈希表 dic 存储中序遍历的值与索引的映射,查找操作的时间复杂度为 O(1)O(1)

    构建左右子树: 开启左右子树递归;

    i - left + root + 1含义为 根节点索引 + 左子树长度 + 1

    返回值: 回溯返回 node ,作为上一层递归中根节点的左 / 右子节点;

    代码

    package com.leetcode.offer.tree;
    
    import com.labuladong.preDefine.TreeNode;
    
    import java.util.HashMap;
    
    public class BuildTree {
        HashMap<Integer, Integer> map = new HashMap<>();//标记中序遍历
        int[] preorder;//保留的先序遍历,方便递归时依据索引查看先序遍历的值
    
        public TreeNode buildTree(int[] preorder, int[] inorder) {
            this.preorder = preorder;
            //将中序遍历的值及索引放在map中,方便递归时获取左子树与右子树的数量及其根的索引
            for (int i = 0; i < inorder.length; i++) {
                map.put(inorder[i], i);
            }
            //三个索引分别为
            //当前根的的索引
            //递归树的左边界,即数组左边界
            //递归树的右边界,即数组右边界
            return recur(0,0,inorder.length-1);
        }
    
        TreeNode recur(int pre_root, int in_left, int in_right){
            if(in_left > in_right) {
                return null;// 相等的话就是自己
            }
            TreeNode root = new TreeNode(preorder[pre_root]);//获取root节点
            int idx = map.get(preorder[pre_root]);//获取在中序遍历中根节点所在索引,以方便获取左子树的数量
            //左子树的根的索引为先序中的根节点+1
            //递归左子树的左边界为原来的中序in_left
            //递归右子树的右边界为中序中的根节点索引-1
            root.left = recur(pre_root+1, in_left, idx-1);
            //右子树的根的索引为先序中的 当前根位置 + 左子树的数量 + 1
            //递归右子树的左边界为中序中当前根节点+1
            //递归右子树的有边界为中序中原来右子树的边界
            root.right = recur(pre_root + (idx - in_left) + 1, idx+1, in_right);
            return root;
        }
    }
    

    ps:感觉这道题好难啊。

    剑指 Offer 26. 树的子结构

    题目

    输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
    
    B是A的子结构, 即 A中有出现和B相同的结构和节点值。
    
    例如:
    给定的树 A:
    
         3
        / 
       4   5
      / 
     1   2
    给定的树 B:
    
       4 
      /
     1
    返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。
    
    示例 1:
    
    输入:A = [1,2,3], B = [3,1]
    输出:false
    示例 2:
    
    输入:A = [3,4,5,1,2], B = [4,1]
    输出:true
    
    

    解题思路:

    根据性质来讲,若树B是树A的子结构,则子结构的根节点可能为树A的任意一个节点。因此判断树B是否是A的子结构需要完成以下两步工作:

    1. 先序遍历A中的每个节点nA(对应函数isSubStructure(A,B)
    2. 判断A中以nA为根节点的子树是否包括树B(对应函数recur(A,B))

    recur(A, B) 函数:

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

    isSubStructure(A, B) 函数:

    1. 特例处理: 当 树 AA 为空 或 树 BB 为空 时,直接返回 falsefalse ;
    2. 返回值: 若树 BB 是树 AA 的子结构,则必满足以下三种情况之一,因此用或 || 连接;
      1. 以 节点 AA 为根节点的子树 包含树 BB ,对应 recur(A, B);
      2. 树 BB 是 树 AA 左子树 的子结构,对应 isSubStructure(A.left, B);
      3. 树 BB 是 树 AA 右子树 的子结构,对应 isSubStructure(A.right, B);

    以上 2. 3. 实质上是在对树 AA先序遍历

    代码:

    class Solution {
        public boolean isSubStructure(TreeNode A, TreeNode B) {
            return (A != null && B != null) && (recur(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B));
        }
        boolean recur(TreeNode A, TreeNode B) {
            if(B == null) return true;
            if(A == null || A.val != B.val) return false;
            return recur(A.left, B.left) && recur(A.right, B.right);
        }
    }
    

    剑指 Offer 27. 二叉树的镜像

    题目

    请完成一个函数,输入一个二叉树,该函数输出它的镜像。
    
    例如输入:
    
         4
       /   
      2     7
     /    / 
    1   3 6   9
    镜像输出:
    
         4
       /   
      7     2
     /    / 
    9   6 3   1
    示例 1:
    
    输入:root = [4,2,7,1,3,6,9]
    输出:[4,7,2,9,6,3,1]
    

    解题思路:

    二叉树镜像定义: 对于二叉树中任意节点 root,设其左 / 右子节点分别为 left, right ;则在二叉树的镜像中的对应 root 节点,其左 / 右子节点分别为 right, left 。

    代码

    方法一:递归

    根据二叉树镜像的定义,考虑递归遍历dfs二叉树,交换每个节点的左右子节点,即可生成二叉树的镜像

    递归解析

    1. 终止条件:当节点root为空,则返回null
    2. 递推工作
      1. 初始化temp节点,用于暂存root的左节点
      2. 开始递归右子节点mirrorTree(root.right).并将返回值作为root的左节点
      3. 开始递归左子节点mirrorTree(temp),并将返回值作为root的右子节点
    3. 返回值:返回当前节点

    为什么要暂存root的左子节点?

    在递归右子节点root.left = mirrorTree(root.right)执行完毕之后,root.left的值已经发生改变,此时递归左子节点会出现问题。

    class Solution {
        public TreeNode mirrorTree(TreeNode root) {
            if(root == null) return null;
            TreeNode tmp = root.left;
            root.left = mirrorTree(root.right);
            root.right = mirrorTree(tmp);
            return root;
        }
    }
    
    

    方法二:栈方法

    利用栈(或队列)遍历树的所有节点node ,并交换每个node 的左 / 右子节点。
    算法流程:

    1. 特例处理: 当 root 为空时,直接返回null ;
    2. 初始化: 栈(或队列),本文用栈,并加入根节点 root 。
    3. 循环交换: 当栈 stack 为空时跳出;
      1. 出栈: 记为 node ;
      2. 添加子节点: 将 node 左和右子节点入栈;
      3. 交换: 交换node 的左 / 右子节点。
    4. 返回值: 返回根节点 root 。
    代码
    package com.leetcode.offer.tree;
    
    import com.labuladong.preDefine.TreeNode;
    
    import java.util.Stack;
    
    public class MirrorTree_2 {
    
        public TreeNode mirrorTree(TreeNode root) {
            if(root==null){
                return null;
            }
            Stack<TreeNode> stack = new Stack<>();
            //首先加入根节点
            stack.push(root);
            // 循环交换
            while (!stack.isEmpty()){
                TreeNode node = stack.pop();
                //添加子节点,将node左右子节点加入栈中
                if(node.left!=null){
                    stack.push(node.left);
                }
                if(node.right!=null){
                    stack.push(node.right);
                }
                //交换节点
                TreeNode temp = node.left;
                node.left=node.right;
                node.right = temp;
            }
            return root;
        }
        public static void main(String[] args) {
            TreeNode a = new TreeNode(4);
            TreeNode al = new TreeNode(2);
            TreeNode ar = new TreeNode(7);
            TreeNode all = new TreeNode(1);
            TreeNode alr = new TreeNode(3);
            TreeNode arl = new TreeNode(6);
            TreeNode arr = new TreeNode(9);
            a.left= al;
            a.right=ar;
            al.left=all;
            al.right=alr;
            ar.left= arl;
            ar.right=arr;
            System.out.println(new MirrorTree_2().mirrorTree(a));
        }
    }
    
    

    剑指 Offer 28. 对称的二叉树

    题目:

    请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。
    
    例如,二叉树 [1,2,2,3,4,4,3] 是对称的。
    
        1
       / 
      2   2
     /  / 
    3  4 4  3
    但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:
    
        1
       / 
      2   2
          
       3    3
    
     
    
    示例 1:
    
    输入:root = [1,2,2,3,4,4,3]
    输出:true
    示例 2:
    
    输入:root = [1,2,2,null,3,null,3]
    输出:false
    

    解题思路:

    对称二叉树定义: 对于树中 任意两个对称节点 L 和 R,一定有:

    1. L.val = R.valL.val=R.val :即此两对称节点值相等。
    2. L.left.val = R.right.valL.left.val=R.right.val :即 L 的 左子节点 和 R 的 右子节点 对称;
    3. L.right.val = R.left.valL.right.val=R.left.val :即 L的 右子节点 和 R 的 左子节点 对称。

    根据以上规律,考虑从顶至底递归,判断每对节点是否对称,从而判断树是否为对称二叉树。

    算法流程:

    isSymmetric(root) :

    • 特例处理: 若根节点 root 为空,则直接返回 true 。
    • 返回值: 即 recur(root.left, root.right) ;

    recur(L, R)

    终止条件

    1. 当 L 和 R同时越过叶节点: 此树从顶至底的节点都对称,因此返回 true ;
    2. 当 L 或 R 中只有一个越过叶节点: 此树不对称,因此返回 false ;
    3. 当节点 L 值 != 节点 R 值: 此树不对称,因此返回 false ;

    递推工作

    1. 判断两节点 L.left和 R.right是否对称,即 recur(L.left, R.right) ;
    2. 判断两节点 L.right和 R.left是否对称,即 recur(L.right, R.left) ;

    返回值: 两对节点都对称时,才是对称树,因此用与逻辑符 && 连接。

    代码:

    package com.leetcode.offer.tree;
    
    import com.labuladong.preDefine.TreeNode;
    public class IsSymmetric {
    
        public boolean isSymmetric(TreeNode root) {
            if(root==null){
                return true;
            }
            return recur(root.left, root.right);
        }
    
        private boolean recur(TreeNode left, TreeNode right) {
            if(left==null &&right==null){
                return true;
            }
            if(left==null || right ==null|| left.val!=right.val){
                return false;
            }
            return recur(left.left, right.right) && recur(left.right, right.left);
        }
    }
    
    

    参考:

    剑指 Offer 27. 二叉树的镜像(递归 / 辅助栈,清晰图解)

    面试题28. 对称的二叉树(递归,清晰图解)

    博客网站 https://yamon.top 个人网站 https://yamon.top/resume GitHub网站 https://github.com/yamonc 欢迎前来访问
  • 相关阅读:
    [转载]ipmitool 对linux服务器进行IPMI管理
    js获取屏幕分辨率
    jquery实现点击块时高亮显示
    [转载]jquery的each()详细介绍
    jQuery用户登录时鼠标焦点事件
    ABAP Programs For Learners
    如何调整ABAP程序的性能
    函数 BAPI_GOODSMVT_CREATE调用实例
    For all entries使用中注意的问题
    用SAP Authority Object 对权限控制
  • 原文地址:https://www.cnblogs.com/chenyameng/p/14988166.html
Copyright © 2011-2022 走看看