二叉树习题总结二

目录

一.按之字形打印二叉树

1.题目

2.思路图解

3.代码

二.二叉搜索树与双向链表

1.题目

2.思路图解

3.代码

三.对称的二叉树

1.题目

2.思路图解

(1)方法1

(2)方法2

3.代码 

四.合并二叉树

1.题目

2.思路图解

3.代码

五.判断是否为二叉搜索树

1.题目

2.思路图解

(1)方法1

(2)方法2

3.代码

六.判断是否为完全二叉树

1.题目

2.思路图解

3.代码

七.判断是否为平衡二叉树

1.题目

2.思路图解

3.代码

八.二叉树中寻找最近公共祖先

1.普通二插树中寻找最近公共祖先

(1)题解

(2)代码

2.二插搜索树中寻找最近公共祖先

(1)题解

(2)代码

九.序列化二叉树

1.题目

2.思路图解

3.代码

十.输出二叉树的右视图

1.题目

2.思路图解

(1)还原二叉树

(2)求二叉树的右视图

3.代码


一.按之字形打印二叉树

1.题目

给定一个二叉树,返回该二叉树的之字形层序遍历,(第一层从左向右,下一层从右向左,一直这样交替)

示例:

2.思路图解

对于奇数层,就从前到后取元素进行打印,然后从后面添加元素(先添加左子树,后添加右子树),和层序遍历的思路一样;

对于偶数层,就要从后往前取元素,然后从前面插入元素(先添加右子树,再添加左子树);

这两个步骤取元素都取的是当前层的元素,都借助一个变量来记录当前层的元素个数。

这里使用双端队列来进行实现。

3.代码

 public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
        Deque<TreeNode> dq = new LinkedList<>();
        dq.addLast(pRoot);
        //记录当前是奇数层还是偶数层
        int index = 1;
        ArrayList<ArrayList<Integer> > res = new ArrayList<>();
        if(pRoot==null) return res;
        while(!dq.isEmpty()) {
            ArrayList<Integer> list = new ArrayList<>();
            int size = dq.size();
            while(size--!=0) {
                if(index%2!=0) {
                    //从左到右
                    TreeNode tmp = dq.removeFirst();
                    list.add(tmp.val);
                    if(tmp.left!=null) {
                        dq.addLast(tmp.left);
                    }
                    if(tmp.right!=null) {
                        dq.addLast(tmp.right);
                    }
                }else {
                    //从右到左
                    TreeNode tmp = dq.removeLast();
                    list.add(tmp.val);
                    if(tmp.right!=null) {
                        dq.addFirst(tmp.right);
                    }
                    if(tmp.left!=null) {
                        dq.addFirst(tmp.left);
                    }
                }
            }
            res.add(list);
            index++;
        }
        return res;
    }

二.二叉搜索树与双向链表

1.题目

2.思路图解

将二插搜索树转化为排序的双向链表时,需要进行中序遍历;在递归时,当遇到第一个节点时,特殊处理,作为链表的头结点;当不是第一个节点时,这时候需要借助一个pre来记录当前结点的前一个节点,因为到当前节点时,可以确定当前结点的前驱和前一个节点的后继,之后将元素进行连接起来。直到二叉树遍历结束,返回头结点。

3.代码

 //记录前一个节点
    TreeNode pre = null;
    //保存第一个节点
    TreeNode head = null;
    public TreeNode Convert(TreeNode pRootOfTree) {
        //递归结束条件
        if(pRootOfTree==null) {
            return null;
        } 
        //处理左子树
        Convert(pRootOfTree.left);
        //处理当前结点(第一个节点单独处理)
        if(pre==null) {
            head =pRootOfTree;
            pre = pRootOfTree;
        }else {
            pRootOfTree.left = pre;
            pre.right = pRootOfTree;
            pre = pRootOfTree;
        }
        //处理右子树
        Convert(pRootOfTree.right);
        //处理结束后返回双向链表头结点
        return head;
    }

三.对称的二叉树

1.题目

2.思路图解

(1)方法1

可以将二叉树进行根左右,根右左遍历两次,将结果保存在两个数组中,然后依次进行比较,如果对应元素都相等,说明是对称的二叉树(比较麻烦)

(2)方法2

同时递归左和右,在递归的过程中就进行比较,如果左右都为空,说明当前子树对称;如果只有一边为空一边不为空,或者左右两边的值不相等,说明不对称;当都不满足时,就继续递归处理左右和右左。

3.代码 

boolean isSymmetrical(TreeNode pRoot) {
        return recursion(pRoot, pRoot);
    }    
    //使用两个根节点,分别表示左,右子树分别进行寻找,整体比较规则有2种
    //根左右对应根右左,根左左对饮根右右
    public boolean recursion(TreeNode root1, TreeNode root2) {
        //当两边同时到达空结点时,说明对称
        if(root1==null && root2==null) return true;
        //如果左为空或者右为空,或者当前左右值不相等,说明不对称
        if(root1==null || root2==null || root1.val!=root2.val) return false;
        //左右,右左的方式进行寻找
        return recursion(root1.left, root2.right) && recursion(root1.right, root2.left);
    }

四.合并二叉树

1.题目

2.思路图解

同时遍历两颗树,如果其中一个子树为空,就返回另外一颗子树(其中包含左右子树都为空返回为null),之后就创建一个新结点来保存两颗树当前结点的值,之后再递归构造新树的左子树和右子树。

3.代码

 public TreeNode mergeTrees (TreeNode t1, TreeNode t2) {
        // write code here
        if(t1==null) return t2;
        if(t2==null) return t1;
        TreeNode root = new TreeNode(t1.val+t2.val);
        root.left = mergeTrees(t1.left, t2.left);
        root.right = mergeTrees(t1.right, t2.right);
        return root;
    }

五.判断是否为二叉搜索树

1.题目

给定一个二叉树根节点,请你判断这棵树是不是二叉搜索树。

二叉搜索树满足每个节点的左子树上的所有节点均严格小于当前节点且右子树上的所有节点均严格大于当前节点。

2.思路图解

(1)方法1

中序遍历,将结果保存在list集合中,然后判断该集合是否为升序。

(2)方法2

记录当前结点的左右边界,初始化根节点的边界为int类型的最大最小值为左右边界;每次判断当前结点是否比左边界大,是否比右边界小;之后再递归左子树时,修改右边界(因为子树中所有结点都比根节点小);递归右子树时,修改左边界(向下遍历时,可以确定最小边界,当前结点小于所有右子树中的节点)。

3.代码

    public boolean isValidBST (TreeNode root) {
        // write code here
        return isValidBST(root, Integer.MIN_VALUE, Integer.MAX_VALUE);
        
    }
    //left为当前root结点区间的左边界,right为右边界
    public boolean isValidBST(TreeNode root, int left, int right) {
        //空树为二插搜索树
        if(root == null) return true;
        //不符合二插搜索树的性质
        if(root.val<=left || root.val>=right) return false;
        //递归向下寻找
        return isValidBST(root.left, left, root.val) && isValidBST(root.right, root.val, right);
    }

六.判断是否为完全二叉树

1.题目

给定一个二叉树,确定他是否是一个完全二叉树。

完全二叉树的定义:若二叉树的深度为 h,除第 h 层外,其它各层的结点数都达到最大个数,第 h 层所有的叶子结点都连续集中在最左边,这就是完全二叉树。(第 h 层可能包含 [1~2h] 个节点)

2.思路图解

按照层序遍历来进行处理,如果在某一层遇到空节点就进行标记,说明已经到了最后一层,之后如果后面还有非空节点,说明非完全二叉树。

3.代码

  public boolean isCompleteTree (TreeNode root) {
        // write code here
        if(root==null) return true;
        Queue<TreeNode> q = new LinkedList<>();
        q.offer(root);
        //标记是否遇到空节点
        boolean flag = false;
        while(!q.isEmpty()) {
            TreeNode tmp = q.poll();
            if(tmp==null) {
                flag = true;
              continue;  
            } 
            if(flag) {
                //说明空节点后还有结点
                return false;
            }
            q.offer(tmp.left);
            q.offer(tmp.right);
        }
        return true;
    }

七.判断是否为平衡二叉树

1.题目

输入一棵节点数为 n 二叉树,判断该二叉树是否是平衡二叉树。

在这里,我们只需要考虑其平衡性,不需要考虑其是不是排序二叉树

平衡二叉树(Balanced Binary Tree),具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

2.思路图解

在求二叉树左右子树的高度时,判断当前左右子树的高度差是否大于1,如果高度差大于1,说明非平衡二叉树,这时候需要终止递归,用-1表示终止条件,直接返回;如果高度差小于1,就返回当前子树的高度,继续进行递归回溯。

3.代码

 public boolean IsBalanced_Solution(TreeNode root) {
        //如果返回-1,说明左右子树不平衡
        if(height(root) != -1) return true;
        return false;
    }
    public int height(TreeNode root) {
        if(root==null) return 0;
        //求左子树的高度
        int left = height(root.left);
        if(left==-1) return -1;
        //求右子树的高度
        int right = height(root.right);
        if(right==-1) return -1;
        //判断左右子树的高度差是否大于1
        if(Math.abs(right-left)>1) return -1;
        //返回当前树的高度
        return Math.max(left, right)+1;
    }

八.二叉树中寻找最近公共祖先

给定一棵二叉树(保证非空)以及这棵树上的两个节点对应的val值 o1 和 o2,请找到 o1 和 o2 的最近公共祖先节点。

1.普通二插树中寻找最近公共祖先

(1)题解

如果当前节点的值等于o1或o2,说明找到了当前结点,直接返回;如果没有找到,返回-1;接着分别向左递归,向右递归;递归结束后,如果左子树中没有找到,结果为-1,说明o1和o2在右子树中,返回right,如果右子树中没有找到,结果为-1,说明在左子树中,返回left;如果left和right都不为-1,说明结果分别在左子树和右子树中,直接返回当前结点为最近公共祖先。

(2)代码

 public int lowestCommonAncestor (TreeNode root, int o1, int o2) {
        // write code here
        //说明在当前路径中没有找到相等的值
        if(root==null) return -1;
        //找到o1或o2的值
        if(root.val==o1 || root.val==o2) {
            return root.val;
        }
        int left = lowestCommonAncestor(root.left, o1, o2);
        int right = lowestCommonAncestor(root.right, o1, o2);
        //如果左子树中没有找到,说明可能在右子树中,直接返回右子树中的查找结果
        if(left == -1) return right;
        //右子树没有找到,可能在左子树中
        if(right == -1) return left;
        //说明o1和o2分别在左右子树中,返回当前结点值作为祖先
        return root.val;
    }

2.二插搜索树中寻找最近公共祖先

可以使用普通的方式寻找,这里利用搜索树的性质寻找

(1)题解

根据二插搜索树的性质,左子树的所有结点都小于当前结点,右子树的节点都大于当前结点;

如果o1比当前结点小 并且 2比当前结点大或者 o1比当前结点大 并且o2比当前结点小,说明o1和o2分别在左右子树中,直接返回当前结点;

如果o1和o2都比当前结点小,继续向左子树递归寻找;如果o1和o2都比当前结点大,继续向右子树递归寻找;没有找到返回-1。

(2)代码

 public int lowestCommonAncestor (TreeNode root, int p, int q) {
        // write code here
        if(root==null) return -1;
        //说明当前p和q分别在当前结点的左右子树中,直接返回当前结点
        if(p<=root.val && q>=root.val || p>=root.val && q<=root.val) {
            return root.val;
        }
        //pq都在左子树中,递归向左寻找
        if(p<=root.val && q<=root.val) 
            return lowestCommonAncestor(root.left, p, q);
        //pq都在右子树中,递归向右寻找
        return lowestCommonAncestor(root.right, p ,q);
    }

九.序列化二叉树

1.题目

请实现两个函数,分别用来序列化和反序列化二叉树,不对序列化之后的字符串进行约束,但要求能够根据序列化之后的字符串重新构造出一棵与原二叉树相同的树。

二叉树的序列化(Serialize)是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树等遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#)

2.思路图解

这里根据层序遍历来实现序列化二叉树,实现的方式:序列化相当于把二叉树遍历的结果保存为字符串,中间可以使用特殊字符来标识每个节点的间隔;反序列化是先将序列化后的字符串先解析为字符串数组,之后通过层序遍历,每次从数组中取出当前结点的左右孩子来还原二叉树,之后借助队列将已经构造好的左右孩子再放到队列中进行下一层的构造。

注意:这里在进行序列化时,对于空子树的节点也要进行保存,之后还原二叉树时,只能对非空节点来构造左右孩子。

3.代码

  TreeNode empty = new TreeNode(-1);
    //序列化
    String Serialize(TreeNode root) {
        if(root == null) return "";
        Queue<TreeNode> q = new LinkedList<>();
        StringBuilder sb = new StringBuilder();
        q.offer(root);
        while(!q.isEmpty()) {
            TreeNode tmp = q.poll();
            sb.append(tmp.val+"_");
            if(tmp!=empty) {
                //说明没有遇到空节点,就继续处理该结点的左和右
                q.offer(tmp.left!=null?tmp.left:empty);
                q.offer(tmp.right!=null?tmp.right:empty);
            }
        }
        return sb.toString();
    }
    //反序列化
    TreeNode Deserialize(String str) {
       if(str=="") return null;
        String[] strs = str.split("_");
        //递归每次从str数组中取出两个元素作为当前元素的左右孩子
        Queue<TreeNode> q = new LinkedList<>();
        //先构造根节点
        TreeNode root = new TreeNode(Integer.valueOf(strs[0]));
        q.offer(root);
        for(int i=1; i<strs.length-1; i+=2) {
            TreeNode tmp = q.poll();
            int left = Integer.valueOf(strs[i]);
            int right = Integer.valueOf(strs[i+1]);
            if(left!=-1) {
                tmp.left = new TreeNode(left);
                q.offer(tmp.left);
            }
            if(right != -1) {
                tmp.right = new TreeNode(right);
                q.offer(tmp.right);
            }
        }
        return root;
    }

十.输出二叉树的右视图

1.题目

请根据二叉树的前序遍历,中序遍历恢复二叉树,并打印出二叉树的右视图。

意思是先根据前,中序遍历还原二叉树,然后输出二叉树每一层最右边的节点

2.思路图解

(1)还原二叉树

因为前序的遍历是根左右,中序是左右根,根据前序遍历的值作为根节点,然后再从中序中找出划分左右子树的中点,然后划分前序和中序的区间范围,递归进行还原二叉树。

(2)求二叉树的右视图

使用Map结构来保存右视图结果,其中key为当前层数,value为当前层最右节点的值。通过两个栈,一个栈保存结点,另一个栈保存当前结点所在的层,之后在处理栈中元素时,如果map中不存在当前元素,说明该结点为最右节点(因为栈中先左节点入栈,最后是右结点入栈,对于已经添加到map中的最右节点后,当前层其他节点就会跳过),最终将map从第一层开始,所有层元素对应的右结点取出即可。

3.代码

 public int[] solve (int[] xianxu, int[] zhongxu) {
        // write code here
        TreeNode root = reBuild(xianxu, zhongxu, 0, 0, xianxu.length-1);
        ArrayList<Integer> list = new ArrayList<>();
        //使用map来判断当前层是否已经添加了最右边的节点
        Map<Integer, Integer> map = new HashMap<>();
        //保存结点,使每次弹出都是当前层的最右边结点
        Stack<TreeNode> nodes = new Stack<>();
        //保存当前深度
        Stack<Integer> depths = new Stack<>();
        //保存树的最深度,之后遍历map使用
        int maxDepth = 0;
        nodes.push(root);
        depths.push(0);
        while(!nodes.isEmpty()) {
            TreeNode node = nodes.pop();
            int depth = depths.pop();
            if(node!=null) {
                maxDepth = Math.max(maxDepth, depth);
                if(map.get(depth)==null) {
                    //说明当前层还没有添加最右结点
                    map.put(depth, node.val);
                }
                nodes.push(node.left);
                nodes.push(node.right);
                depths.push(depth+1);
                depths.push(depth+1);
                }
        }
        int[] res = new int[map.size()];
        for(int i=0; i<map.size(); i++) {
            res[i] = map.get(i);
        }
        return res;
    }

    public TreeNode reBuild(int[] pre, int[] mid, int index, int left, int right) {
        //说明越界
        if(index>=pre.length || left>right) {
            return null;
        }
        //将当前前序元素值构造成一个结点
        TreeNode root = new TreeNode(pre[index]);
        //从中序数组中找中点
        int i = left;
        for(; i<=right; i++) {
            if(pre[index] == mid[i]) {
                break;
            }
        }
        root.left = reBuild(pre, mid, index+1, left, i-1);
        root.right = reBuild(pre, mid, index+(i-left+1), i+1, right);
        return root;
    }

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值