合并二叉树
例题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,如果左右子树都不为空则该节点是最近的公共祖先。如图:
2.遍历的节点就是p或q,则它的左右子树一个不为空,则它就是祖先。如图:
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.二叉树的公共祖先:递归中判断该节点的左右子树是否出现要找的节点。怎么遍历?采用后序自带的回溯特性来自底向上判断。