目录
一、二叉树的修改与改造问题
1、翻转二叉树
2、从中序和后序序列构造二叉树
3、最大二叉树
4、合并二叉树
二、二叉搜索树问题
1、二叉搜索树中树的搜索
1)递归法:
2)迭代法:
2、验证二叉搜索树
3、二叉搜索树的最小绝对差
4、二叉搜索树中的众数
5、把二叉搜索树转为累加树
给你一棵二叉树的根节点 root
,翻转这棵二叉树,并返回其根节点。
这道题目大家第一次做可能会觉得它很简单,左换右,一直递归不就行了
但如果沉下心来思考这道问题的本质 我相信大家一定有所收获!
之前我们介绍了各种遍历二叉树的方式,如果要翻转这颗二叉树 我们应该采取哪一种遍历方式呢?
其实这道题采用前,中,后都可以 只要在遍历时交换左右节点即可
但是中序的时候有些麻烦 因为 我们左子树的节点已经完成了翻转 而此时左右交换,中序遍历恰好需要到右子树遍历 这就导致原先的左子树被翻转了两次 而右子树被交换到了左子树 还没有完成翻转!
前序遍历代码:
public TreeNode invertTree(TreeNode root) {
if(root==null){//root为Null的时候我们什么都不需要做
return null;
}
TreeNode temp=root.left;//交换左右节点
root.left=root.right;
root.right=temp;
invertTree(root.left);
invertTree(root.right);
return root;
}
后序遍历代码:
public TreeNode invertTree(TreeNode root) {
if(root==null){//root为Null的时候我们什么都不需要做
return null;
}
invertTree(root.left);
invertTree(root.right);
TreeNode temp=root.left;//交换左右节点
root.left=root.right;
root.right=temp;
return root;
}
中序遍历实现交换的代码也很简单,不过是 左 中 右 的顺序改为 左 中 左 因为交换过后右子树被交换到了左子树 我们需要对左子树进行处理
public TreeNode invertTree(TreeNode root) {
if(root==null){//root为Null的时候我们什么都不需要做
return null;
}
invertTree(root.left);
TreeNode temp=root.left;//交换左右节点
root.left=root.right;
root.right=temp;
invertTree(root.left);
return root;
}
层序遍历的代码也是一样的 只要在遍历到结点时 交换它的左右子树就可以了
注意:很多同学写熟了层序遍历之后 经常把从队列中弹出的元素赋值给root,这样做本身是没有任何问题的,但本题要求最后返回根节点 而经过层序遍历后 我们的root已经变为了队列中最后一个结点 因此我们一开始就要记录一个根节点
public TreeNode invertTree(TreeNode root) {
if(root==null){//root为Null的时候我们什么都不需要做
return null;
}
Deque queue=new LinkedList<>();
queue.offer(root);
TreeNode res=root;
while (!queue.isEmpty()){
int size=queue.size();
for (int i=0;i
这道题当然还可以用非递归的写法来解,本质上其实还是前中后的迭代写法,对迭代遍历印象不深的同学建议看我的上一篇文章,这里就不再过多赘述。
首先回忆一下如何根据两个顺序构造一个唯一的二叉树,相信理论知识大家应该都清楚,就是以 后序数组的最后一个元素为切割点,先切中序数组,根据中序数组,反过来在切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。
如果让我们肉眼看两个序列,画一颗二叉树的话,应该分分钟都可以画出来。
我们看一下这棵树的中序 和后序遍历的数组:
来看一下一共分几步:
第一步:如果数组大小为零的话,说明是空节点了。
第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。
第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点
第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)
第五步:切割后序数组,切成后序左数组和后序右数组
第六步:递归处理左区间和右区间
题解来源:图解构造二叉树之中序+后序 - 从中序与后序遍历序列构造二叉树 - 力扣(LeetCode) (leetcode-cn.com)https://leetcode-cn.com/problems/construct-binary-tree-from-inorder-and-postorder-traversal/solution/tu-jie-gou-zao-er-cha-shu-wei-wan-dai-xu-by-user72/
public TreeNode buildTree(int[] inorder, int[] postorder) {
int is=0;
int ie=inorder.length-1;
int ps=0;
int pe=postorder.length-1;
TreeNode root=buildTree(inorder,postorder,is,ie,ps,pe);
return root;
}
public TreeNode buildTree(int[] inorder,int [] postorder,int is,int ie,int ps,int pe){
if (ie
但是这个代码还存在一些问题,由于我们要拿后序数组中最后一个元素在中序中查找(每一个元素) 这样的效率是很低的 因此我们在一开始就把所有的元素和它在中序中的下标存在hash表里 要用的时候直接取!
HashMap map=new HashMap<>();
public TreeNode buildTree(int[] inorder, int[] postorder) {
int is=0;
int ie=inorder.length-1;
int ps=0;
int pe=postorder.length-1;
for (int i=0;i
这道题做完后,大家可以尝试从前序和中序序列构造二叉树 本质上和本题没有任何区别,无非是后序从后找根,前序从前找根
HashMap map=new HashMap<>();
public TreeNode buildTree(int[] preorder, int[] inorder) {
//通过前序中序其实和中序后序没有任何区别
//中序后序是取后序的最后一个元素
//前序中序自然是取前序的第一个元素
for (int i=0;i
这道题一看问题的描述 ,就知道和上一道从前序 中序构造二叉树的问题本质没有任何区别,都是要分割数组,不断递归。
public class Solution {
public TreeNode constructMaximumBinaryTree(int[] nums) {
return helper(nums,0,nums.length-1);
}
public TreeNode helper(int[] nums,int s,int e){
if (emax){
maxIndex=i;
max=nums[i];
}
}
TreeNode root=new TreeNode(max);//递归的构造左右子树
root.left=helper(nums,s,maxIndex-1);
root.right=helper(nums,maxIndex+1,e);
return root;
}
}
这道题十分简单,我们只需要确保两颗二叉树的遍历方式是相同的,在遍历到一个结点时,新建一个结点替代这两个结点,然后分别遍历左右子树即可。
这里仅给出前序遍历的代码(中序,后序只是顺序上的不一致)
public class Solution {
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
if (root1==null&&root2==null){//如果两颗树都是空 那么就返回空
return null;
}
if (root1==null){//只有一棵树不为空时 返回不为空的那颗树
return root2;
}
if (root2==null){
return root1;
}
TreeNode node=new TreeNode(root1.val+root2.val);//新建结点 然后构建左右子树 本质上是前序遍历
node.left=mergeTrees(root1.left,root2.left);
node.right=mergeTrees(root1.right,root2.right);
return node;
}
}
在我们遇到二叉搜索树问题时,我们必须要记住一点:二叉搜索树的中序遍历是有序的,这一点是我们求解二叉搜索树的关键!
二叉搜索树是一个有序树:
若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
它的左、右子树也分别为二叉搜索树
这就决定了,二叉搜索树,递归遍历和迭代遍历和普通二叉树都不一样。
确定递归函数的参数和返回值
递归函数的参数传入的就是根节点和要搜索的数值,返回的就是以这个搜索数值所在的节点。
代码如下:
TreeNode* searchBST(TreeNode* root, int val)
确定终止条件
如果root为空,或者找到这个数值了,就返回root节点。
if (root == NULL || root->val == val) return root;
确定单层递归的逻辑
看看二叉搜索树的单层递归逻辑有何不同。
因为二叉搜索树的节点是有序的,所以可以有方向的去搜索。
如果root->val > val,搜索左子树,如果root->val < val,就搜索右子树,最后如果都没有搜索到,就返回NULL。
代码如下:
if (root->val > val) return searchBST(root->left, val); // 注意这里加了return
if (root->val < val) return searchBST(root->right, val);
return NULL;
这里可能会疑惑,在递归遍历的时候,什么时候直接return 递归函数的返回值,什么时候不用加这个 return呢。
我们在之前讲了,如果要搜索一条边,递归函数就要加返回值,这里也是一样的道理。
因为搜索到目标节点了,就要立即return了,这样才是找到节点就返回(搜索某一条边),如果不加return,就是遍历整棵树了。
整体代码如下:
class Solution {
public TreeNode searchBST(TreeNode root, int val) {
if(root==null||root.val==val) return root;
if (root.val
提到二叉树遍历的迭代法,可能立刻想起使用栈来模拟深度遍历,使用队列来模拟广度遍历。
对于二叉搜索树可就不一样了,因为二叉搜索树的特殊性,也就是节点的有序性,可以不使用辅助栈或者队列就可以写出迭代法。
对于一般二叉树,递归过程中还有回溯的过程,例如走一个左方向的分支走到头了,那么要调头,在走右分支。
而对于二叉搜索树,不需要回溯的过程,因为节点的有序性就帮我们确定了搜索的方向。
例如要搜索元素为3的节点,我们不需要搜索其他节点,也不需要做回溯,查找的路径已经规划好了。
中间节点如果大于3就向左走,如果小于3就向右走,如图:
所以迭代法代码如下:
public class Solution {
public TreeNode searchBST(TreeNode root, int val) {
while (root!=null){
if (val>root.val){
root=root.right;
}
if (val
写到这道题的时候,很多同学直接想到 我只要遍历每个结点 保证左小于它 右大于它 不就好了
这样的思路看似完美 但其实有很大问题 因为二叉搜索树要求左子树所有结点都小于它 而右子树所有结点都大于它
我们可以对上述思路很轻易的举出反例:
6明显大于10,所以不能出现在右子树
因此我们想到利用二叉搜索树进行中序遍历 定义前一个pre 只要当前结点<=它我们就返回false,否则每个节点都遍历完成 确认无误后我们返回true
这道题其实有很多坑 很多同学写下如下的代码:
public class Solution {
int pre=Integer.MIN_VALUE;
public boolean isValidBST(TreeNode root) {
if (root==null) return true;
isValidBST(root.left);
if (root.val<=pre) return false;
pre=root.val;
return isValidBST(root.right);
}
}
这代码其实有很多问题 他真正能检测出一棵树是不是二叉搜索树吗?并不能!为什么?
这的确是中序遍历 但是 ,假如左子树已经不是二叉搜索树了 而我们返回的却永远是右子树的检测!因此我们需要定义一个变量将左子树的检测保存起来!
public class Solution {
int pre=Integer.MIN_VALUE;
public boolean isValidBST(TreeNode root) {
if (root==null) return true;
boolean left=isValidBST(root.left);
if (root.val<=pre) return false;
pre=root.val;
boolean right=isValidBST(root.right);
return left&&right;
}
}
但仍然存在问题,官方的测试用例中有第一个结点的值为integer的最小值的可能,那我们换成long的最小值不就好了,确实可以这么做,但假如我们第一个结点恰好是long的最小值呢?
所以这种方案缺乏严密性,我们应把pre定义为前驱结点 默认他是null
TreeNode pre=null;
public boolean isValidBST(TreeNode root) {
if (root==null) return true;
boolean left=isValidBST(root.left);
if (pre==null||root.val>pre.val){
pre=root;
}else {
return false;
}
boolean right=isValidBST(root.right);
return left&&right;
}
这道题看起来十分复杂,不同结点之间的最小差值,那么我需要找每一种结点的匹配项 这个复杂度稳稳地O(N^2) 但其实我们发现本题是一颗二叉搜索树 它的中序遍历一定是升序地 而最小地差值一定出现在两个相邻地元素
public class Solution {
public int min=Integer.MAX_VALUE;//定义最小差值
public TreeNode pre=null;//定义前一个结点
public int getMinimumDifference(TreeNode root) {
if (root==null){
return -1;//本质上我们什么都不需要做
}
getMinimumDifference(root.left);
if (pre==null){//中序遍历地第一个结点
pre=root;
}else {
min=Math.min(min,root.val-pre.val);
pre=root;
}
getMinimumDifference(root.right);
return min;
}
}
这道题看起来似乎要用到hashmap存储对应的值和出现的次数然后再逐一比较出现的次数大小 最后返回众数 但还是由于它是二叉搜索树,所以中序遍历是有序的 既然有序 那么相同的数 肯定是连在一起的 因此我们很好统计数量
public class Solution {
List answer=new ArrayList<>();//用于加入元素 最后返回数组
int base,count,max;
public int[] findMode(TreeNode root) {
dfs(root);
int[] res=new int[answer.size()];//把list中的元素加到数组中
for (int i=0;imax){//大于需要清空 然后加进去
max=count;//更新最大值
answer.clear();
answer.add(base);
}
}
}
一看到累加树,相信很多小伙伴都会疑惑:如何累加?遇到一个节点,然后在遍历其他节点累加?怎么一想这么麻烦呢。
然后再发现这是一颗二叉搜索树,二叉搜索树啊,这是有序的啊。
那么有序的元素如果求累加呢?
其实这就是一棵树,大家可能看起来有点别扭,换一个角度来看,这就是一个有序数组[2, 5, 13],求从后到前的累加数组,也就是[20, 18, 13],是不是感觉这就简单了。
为什么变成数组就是感觉简单了呢?
因为数组大家都知道怎么遍历啊,从后向前,挨个累加就完事了,这换成了二叉搜索树,看起来就别扭了一些是不是。
那么知道如何遍历这个二叉树,也就迎刃而解了,从树中可以看出累加的顺序是右中左,所以我们需要反中序遍历这个二叉树,然后顺序累加就可以了。
本题依然需要一个pre指针记录当前遍历节点cur的前一个节点,这样才方便做累加。
这里很明确了,不需要递归函数的返回值做什么操作了,要遍历整棵树。
同时需要定义一个全局变量pre,用来保存cur节点的前一个节点的数值,定义为int型就可以了。
代码如下:
int pre; // 记录前一个节点的数值
void traversal(TreeNode* cur)
遇空就终止。
if (cur == NULL) return;
注意**要右中左来遍历二叉树**, 中节点的处理逻辑就是让cur的数值加上前一个节点的数值。
class Solution {
TreeNode pre=null;
public TreeNode convertBST(TreeNode root) {
if (root==null){
return null;
}
convertBST(root.right);
if (pre==null){
//root的val不变
}else {
root.val+=pre.val;
}
pre=root;
convertBST(root.left);
return root;
}
}