二叉树习题4:合并二叉树、二叉搜索树中的搜索、验证二叉树、二叉搜索树的最小绝对差、二叉搜索树中的众数、二叉树的最近公共祖先

合并二叉树

例题617:给你两棵二叉树: root1 和 root2 。

想象一下,当你将其中一棵覆盖到另一棵之上时,两棵树上的一些节点将会重叠(而另一些不会)。你需要将这两棵树合并成一棵新二叉树。合并的规则是:如果两个节点重叠,那么将这两个节点的值相加作为合并后节点的新值;否则,不为 null 的节点将直接作为新二叉树的节点。

返回合并后的二叉树。

注意: 合并过程必须从两个树的根节点开始。

示例

处理二叉树节点,其实就是遍历二叉树的过程中的某些操作,最重要的是怎么遍历二叉树。

递归法
class Solution {
    public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
        //新建节点消耗内存
        /*
        TreeNode root=new TreeNode();
        if(root1!=null && root2!=null){
            root.val=root1.val+root2.val;
        }
        */
        /*
        if(root1!=null && root2==null){
            root.val=root1.val;
        }
        if(root1==null && root2!=null){
            root.val=root2.val;
        }
        */
        //题目要求不为null的节点直接作为新二叉树的节点,不能新建一个节点改值
        if(root1==null) return root2;
        if(root2==null) return root1;
         //以root1为基准,直接修改t1的值
        root1.val+=root2.val;//中
        root1.left=mergeTrees(root1.left,root2.left);//左
        root1.right=mergeTrees(root1.right,root2.right);//右
        return root1;
    }
}
迭代法:使用栈
class Solution {
    // 使用栈迭代
    public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
        if (root1 == null) {
            return root2;
        }
        if (root2 == null) {
            return root1;
        }
        Stack<TreeNode> stack = new Stack<>();
        stack.push(root2);
        stack.push(root1);
        while (!stack.isEmpty()) {
            TreeNode node1 = stack.pop();
            TreeNode node2 = stack.pop();
            node1.val += node2.val;
            if (node2.right != null && node1.right != null) {
                stack.push(node2.right);
                stack.push(node1.right);
            } else {
                if (node1.right == null) {
                    node1.right = node2.right;
                }
            }
            if (node2.left != null && node1.left != null) {
                stack.push(node2.left);
                stack.push(node1.left);
            } else {
                if (node1.left == null) {
                    node1.left = node2.left;
                }
            }
        }
        return root1;
    }
}
层序遍历:使用队列同时处理两个树的节点
class Solution {
    // 使用队列迭代
    public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
        if (root1 == null) return root2;
        if (root2 ==null) return root1;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root1);
        queue.offer(root2);
        while (!queue.isEmpty()) {
            TreeNode node1 = queue.poll();
            TreeNode node2 = queue.poll();
            // 此时两个节点一定不为空,val相加
            node1.val = node1.val + node2.val;
            // 如果两棵树左节点都不为空,加入队列
            if (node1.left != null && node2.left != null) {
                queue.offer(node1.left);
                queue.offer(node2.left);
            }
            // 如果两棵树右节点都不为空,加入队列
            if (node1.right != null && node2.right != null) {
                queue.offer(node1.right);
                queue.offer(node2.right);
            }
            // 若node1的左节点为空,直接赋值
            if (node1.left == null && node2.left != null) {
                node1.left = node2.left;
            }
            // 若node1的右节点为空,直接赋值
            if (node1.right == null && node2.right != null) {
                node1.right = node2.right;
            }
        }
        return root1;
    }
}

二叉搜索树中的搜索

例题700:给定二叉搜索树(BST)的根节点 root 和一个整数值 val。

你需要在 BST 中找到节点值等于 val 的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 null 。
示例

递归法
class Solution {
    public TreeNode searchBST(TreeNode root, int val) {
if(root==null || root.val==val) return root;
TreeNode res=new TreeNode();
if(root.val>val) res=searchBST(root.left,val);//如果输入的是root.left会出错
if(root.val<val) res=searchBST(root.right,val);
return res;
    }
}
迭代法
//一般递归过程中带有回溯,但对于二叉搜索树,不需要回溯过程,因为节点的有序性就帮我们确定了搜索方向。
class Solution {
    public TreeNode searchBST(TreeNode root, int val) {
while(root!=null){
    if(root.val>val) root=root.left;
    else if(root.val<val) root=root.right;
    else return root;
}
return null;
    }
}

验证二叉树

例题98:给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。

有效 二叉搜索树定义如下:

节点的左子树只包含 小于 当前节点的数。
节点的右子树只包含 大于 当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。

示例

中序遍历下的二叉搜索树,得到的是一个有序数组
需要注意有陷阱:
1.比较的不是左节点小于根节点,根节点小于右节点就结束了,而是左子树都小于根节点,右子树都大于根节点。如图:
在这里插入图片描述

采用中序递归得到的遍历序列应该是递增的,如果数组不递增则说明有问题返回false
class Solution {
     ArrayList<Integer> res=new ArrayList<>();
    public boolean isValidBST(TreeNode root) {
if(root==null) return true;
traversal(root);
for(int i=0;i<res.size()-1;i++)
{
    if(res.get(i)>=res.get(i+1)){
        return false;
    }
}
return true;
    }

     public void traversal(TreeNode root){
        if(root==null) return;
        traversal(root.left);
        res.add(root.val);
        traversal(root.right);
    }
}

二叉搜索树的最小绝对差

例题530:给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值 。

差值是一个正数,其数值等于两值之差的绝对值。

示例
1.与上题类似,采用中序遍历得到递增的节点值数组,然后依次比较相邻节点的差值,因为二叉搜索树一定是父亲与孩子结点差值可能最小。

class Solution {
    ArrayList<Integer> nums=new ArrayList<>();
    int min=100000;
    public int getMinimumDifference(TreeNode root) {
//递归得到递增数组
        if(root==null) return 0;
        traversal(root);
        for(int i=nums.size()-1;i>0;i--){
            if(nums.get(i)-nums.get(i-1)<=min){
                min=nums.get(i)-nums.get(i-1);
            }
        }
        return min;
    }

      public void traversal(TreeNode root){
        if(root==null) return;
        traversal(root.left);
        nums.add(root.val);
        traversal(root.right);
    }
}

2.也可以在中序递归时直接计算,记录当前节点与上一个节点的值

class Solution {
TreeNode pre;//记录前一个节点
    int mi=100000;
    public int getMinimumDifference(TreeNode root){
        if(root==null) return 0;
        traversal(root);
        return mi;
    }

    public void traversal(TreeNode root){
        if(root==null) return;
        traversal(root.left);
        //中的处理逻辑
        if(pre!=null){
            mi=Math.min(mi,root.val-pre.val);
        }
        pre=root;//更新前一个节点
        traversal(root.right);
    }
}

3.使用栈进行统一的迭代法

class Solution {
    public int getMinimumDifference(TreeNode root) {
        Stack<TreeNode> stack = new Stack<>();
        TreeNode pre = null;
        int result = Integer.MAX_VALUE;

        if(root != null)
            stack.add(root);
        while(!stack.isEmpty()){
            TreeNode curr = stack.peek();
            if(curr != null){
                stack.pop();
                if(curr.right != null)
                    stack.add(curr.right);
                stack.add(curr);
                stack.add(null);
                if(curr.left != null)
                    stack.add(curr.left);
            }else{
                stack.pop();
                TreeNode temp = stack.pop();
                if(pre != null)
                    result = Math.min(result, temp.val - pre.val);
                pre = temp;
            }
        }
        return result;
    }
}

二叉搜索树中的众数

例题501:给你一个含重复值的二叉搜索树(BST)的根节点 root ,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。

如果树中有不止一个众数,可以按 任意顺序 返回。

假定 BST 满足如下定义:

结点左子树中所含节点的值 小于等于 当前节点的值
结点右子树中所含节点的值 大于等于 当前节点的值
左子树和右子树都是二叉搜索树

示例

暴力法:将节点值以及出现频率存入哈希表,找到哈希表中最大的值,也就是最高的频率再找到对应的键,也就是频率最高的节点值
class Solution {
    //使用中序遍历得到哈希表
    HashMap<Integer,Integer> hmap=new HashMap<>();//用来存放节点值与频率的哈希表
    ArrayList<Integer> nums=new ArrayList<>();
    public int[] findMode(TreeNode root){
        if(root==null) return null;
        traversal(root);
        int mx=0;
        //找到哈希表中最大的值也就是最高的频率
        for(Integer vm:hmap.values()){
            if(vm>=mx)
            mx=vm;
        }
        //在哈希表中的对中循环查找最大值对应的键  
        Set<Map.Entry<Integer, Integer>> set = hmap.entrySet();
        for(Map.Entry<Integer,Integer> entry:set){
         if(entry.getValue()==mx){
             nums.add(entry.getKey());
         }
        }


        int[] res=new int[nums.size()];//创建动态数组
        Object[] objectArray=nums.toArray();
        for(int i=0;i<objectArray.length;i++){
            res[i]=(int) objectArray[i];
        }
        return res;
    }

    public void traversal(TreeNode root){
    if(root==null) return;
    traversal(root.left);
    hmap.put(root.val,hmap.getOrDefault(root.val,0)+1);//这句代码的作用是统计二叉树中每个节点值的出现次数,将结果存储在 HashMap 中。如果节点值已经存在于 HashMap 中,就增加对应值的计数;如果节点值不存在于 HashMap 中,就创建一个新的键值对,初始计数为 1。
    traversal(root.right);
    }
}

1.getOrDefault(Object key,Object defaultValue)函数:是Map接口中的一个方法,如果map集合中有这个key时,就使用这个key值,否则返回默认值
2.HashMap.Entry:哈希表的实体,可用来获取值与键
3.Object[] objectArray=nums.toArray():toArray()是List接口的一个方法,用于将列表中的元素转换为Object数组。转换后的数组与原数组有相同的元素与存储顺序。

二叉树的最近公共祖先

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

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

后序遍历(左右中)就是天然的回溯,根据左右子树的返回值来处理中间节点的逻辑。

怎么判断该节点是节点p和节点q的公共祖先呢?
1.如果节点的左子树出现节点p,右子树出现节点q,或者相反的情况,那么该节点就是两节点的公共祖先。这种情况在遍历的过程中,如果左子树遇到p则返回p,右子树如果遇到q则返回q,如果左右子树都不为空则该节点是最近的公共祖先。如图:
情况1
2.遍历的节点就是p或q,则它的左右子树一个不为空,则它就是祖先。如图:
情况2

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if(root==null) return root;
        TreeNode res=new TreeNode(0);
        res=traversal(root,p,q);
        return res;
    }

        public TreeNode traversal(TreeNode root,TreeNode p,TreeNode q){
            if(root==null || root==q || root==p) return root;//结束条件非常重要
TreeNode left=traversal(root.left,p,q);
TreeNode right=traversal(root.right,p,q);
if(left==null && right==null) {
    return null;
}
else if(left==null && right!=null){
    return right;
}
else if(left!=null && right==null){
    return left;
}
else {
    return root;
}
    }
}

二叉树周末总结

1.合并二叉树:同时处理两个树的节点,可以新建一个树,但更好的是直接在原来的一棵树上操作。可以用层序、递归、迭代遍历两个二叉树,重要的就是在遍历的过程中处理节点。
2.二叉搜索树:碰到二叉搜索树就牢记:中序遍历就是在有序数组上操作。
3.验证二叉搜索树:暴力法:中序遍历得到一个递增数组。递归比较时,不光是左节点右节点小于或大于根节点,而是左子树右子树都要小于或大于根节点。
4.二叉搜索树的最小绝对差值:要知道二叉树父亲与孩子节点是差值最小的可能出处,因此直接比较中序遍历得到的递增数组每两个相邻节点的差值。
5.二叉树的众数:暴力法:用哈希表存放节点值与出现频率,找到最大频率的值,再求对应的键。
6.二叉树的公共祖先:递归中判断该节点的左右子树是否出现要找的节点。怎么遍历?采用后序自带的回溯特性来自底向上判断。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值