Unit 3 二叉树
Q1:二叉树的先序遍历
Q2:二叉树的中序遍历
Q3:二叉树的后序遍历
Q4:二叉树的层次遍历
Q5:求二叉树的高度
Q6:逆时针打印二叉树的边界结点
Q7:二叉树的序列化和反序列化
Q8:莫里斯二叉树遍历法
Q9:在二叉树中,找到一条最长的路径,该路径上点的累加和为给定值k
Q10:找二叉树中的最大二叉排序子树
Q11:找到二叉树中的最大BST拓扑,返回最大BST拓扑的节点数
Q12:二叉树ZigZag打印
Q13:BST中有两个结点调换了位置。找出这两个结点并恢复原BST
Q14:判断T1是否包含T2全部的拓扑结构
Q15:判断T1树中是否包含与T2完全相同的子树
Q16:判断二叉树是否是平衡二叉树
Q17:根据后序数组重建二叉排序树
Q18:判断一棵树是否为二叉排序树
Q19:判断一棵树是否为完全二叉树
Q20:根据中序遍历序列生成平衡BST
Q21:含父节点的二叉树中,找到给定结点在中序遍历下的后继结点。
Q22:二叉树中找到两个结点的最近公共祖先
Q22_2:BST中找到两个结点的最近公共祖先
Q23:求二叉树中节点间的最大距离
Q24:先序、中序构造二叉树。
Q25:使用中序、后序数组构造一棵二叉树
Q26:给定一有序中序序列,统计可生成多少棵不同的二叉树
Q27:给定一有序中序序列{1,2,…,N},生成所有可能不同的BST
Q28:二叉树的镜像
Q29:判断二叉树是否对称
Q30:二叉树中和为给定值的路径
Q31:二叉搜索树中的第k个结点
Q32:将BST转换成双向链表
unit 3 Q1:二叉树的先序遍历
time:2019/07/30
思路:
1.递归方法,略。
2,非递归方法,使用栈辅助,与层次遍历很像。(层次遍历用队列,先序遍历用栈)
- 先入根
- 弹出栈顶元素,访问之。将其右孩子、左孩子依次入栈
- 栈不空,循环
Q:入栈时为什么先入右孩子,再入左孩子?
A:栈先进后出。如果先入右再入左,则出栈访问时反过来,先出左再出右
代码:U3Q1_PreOrder.java
unit 3 Q2:二叉树的中序遍历
time:2019/07/30
思路:
1.递归方法
public void inOrder_Recur(BNode T){
if(T!=null){
inOrder_Recur(T.lchild);
visit(T);
inOrder_Recur(T.rchild);
}
}
2.非递归方法,也是用栈辅助。
(1).从根结点开始,一直向左走到头
(2).到头后(即p==null),从栈中取栈顶结点(即空结点的根结点),访问之
(3).接着遍历此根右孩子
(4).栈不空或者p不空,循环
PS:进入循环有两种情况:
p不空:则p的根结点还能继续向左走,此时继续向左
p空:(1)栈不空,代表左分支到头了该取其根了 (2)栈为空时,所有结点都遍历完了,该出循环了
public void inOrder_NonRecur(BNode T){
Stack<BNode> stack=new Stack<BNode>();
BNode p=T;
//栈不空或者p不空时循环
while(p!=null||!stack.isEmpty()){
if(p!=null){
stack.push(p);
p=p.lchild;
}else{
p=stack.pop();
visit(p);
p=p.rchild;
}
}
}
unit 3 Q3:二叉树的后序遍历
time:2019/07/31
思路:
1.递归方法,略
2.非递归方法:
(1).设置栈s1反着先序遍历,可以得到 “根、右、左”,遍历时将结果压入s2
(2).设置栈s2对 “根、右、左” 逆置。反着先序遍历后,不断对s2出栈,得到 “左、右、根”
代码:U3Q3_PostOrder.java
unit 3 Q4:二叉树的层次遍历
剑指offer 32 牛客链接
time:2019/07/31
思路:
用队列实现。
1.头结点入队。
2.队头元素出队时,其左、右孩子入队。
3.队空,while循环停止。
代码:
public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
ArrayList<Integer> res=new ArrayList<Integer>();
Queue<TreeNode> queue=new LinkedList<TreeNode>();//队列初始化
if(root==null)
return res;
TreeNode p=root;
queue.add(p);
while(!queue.isEmpty()){
p=queue.poll();
res.add(p.val);
if(p.left!=null)
queue.add(p.left);
if(p.right!=null)
queue.add(p.right);
}
return res;
}
date:2020/02/12 更新:
剑指offer 32(加) 按行打印二叉树(牛客链接)
从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
思路:
模仿下一题Q5求高度,魔改。求高度一定要熟练!
代码:
ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
Queue<TreeNode> queue=new LinkedList<TreeNode>();
ArrayList<ArrayList<Integer>> res=new ArrayList<ArrayList<Integer>>();
if(pRoot==null)
return res;
TreeNode p=pRoot,last=pRoot;
queue.add(p);
while(!queue.isEmpty()){
int count=0;
int layer_len=queue.size();
ArrayList<Integer> layer_res=new ArrayList<Integer>();
while(count<layer_len){
p=queue.poll();
count++;
layer_res.add(p.val);
if(p.left!=null)
queue.add(p.left);
if(p.right!=null)
queue.add(p.right);
}
res.add(layer_res);
}
return res;
}
unit 3 Q5:求二叉树的高度
剑指offer Q55_1 牛客链接
time:2019/07/31
思路:
1.递归方法。
(1).一层层下到底部,BNode到空为终止条件,返回0
(2).每下到一个结点,计算该结点的左子树高度和右字数高度。
取左右子树中高度较大者,+1后return,作为本结点对应子树的高度
(3).层层返回至头结点,即可求头结点对应子树、即整棵树的高度
//递归求二叉树高度
public int getHeight(BNode T){
if(T==null) return 0;
int leftH=getHeight(T.lchild);
int rightH=getHeight(T.rchild);
int subH=(leftH>rightH)?(leftH+1):(rightH+1);//左右子树中高度较大者,+1即是本结点的树高度
return subH;
}
2.非递归方法。
以层次遍历为模板。
(1).设置计数器count初值为0,设置layer_len赋给其进入层次循环前的队列长度,设置level记录二叉树的已遍历过的层数。
(2).两层while循环,第一层循环各层,栈空即中止。
第二层循环一层中的各结点,每个结点皆出队,visit。左孩子不空将左孩子入队,右孩子不空则将右孩子入队。
count累加至layer_len时该层循环停止。
(3).每次出第二层循环,代表一层结点又循环完了,level自增1。
(4).第一层while循环栈空跳出,则level即是二叉树的高度
public int TreeDepth(TreeNode T) {
if(T==null)
return 0;
Queue<TreeNode> queue=new ArrayDeque<TreeNode>();
TreeNode p=T;
int level=0;
queue.add(p);
//第一个while循环遍历每一层
while(!queue.isEmpty()){
int count=0;//记录当前层走了多少
int layer_len=queue.size();//当遍历完当前层后,队列中都是下一层结点,则队列长度是下一层结点的个数
while(count<layer_len){
p=queue.poll();
count++;
if(p.left!=null)
queue.add(p.left);
if(p.right!=null)
queue.add(p.right);
}
level++;
}
return level;
}
Q:为什么进入层次循环前的队列长度就是下一层的结点个数?
A:当遍历完当前层后,队列中都是下一层结点,则队列的长度是下一层结点的个数
代码:U3Q5_GetHeight.java
unit 3 Q6:逆时针打印二叉树的边界结点
要求:边界结点的定义:(1)头结点 (2)叶节点 (3)每一层的最左、最右结点
时间复杂度O(N),额外空间复杂度O(h)
time:2019/08/02
思路:
考察综合编码能力
1.使用层次遍历得到每一层的最左、最右结点,存入 height行 2列的LR_edge数组内。get_LREdge(T)函数实现
2.从上到下,从LR_edge中打印所有的层最左结点
3.使用加入高度和LR_edge为参数的递归前序遍历,遍历时与LR_edge对照,打印不是该层最左、最右结点的叶节点。
printLeafNotEdge(T,0,LR_edge)实现
4.从下到上,从LR_edge中打印所有的层最右结点
代码:U3Q6_PrintEdge.java
unit 3 Q7:二叉树的序列化和反序列化
剑指offer 37 牛客链接
二叉树的序列化:将二叉树记录为一个字符串
二叉树的反序列化:将字符串反向生成一个二叉树
time:2019/08/02
思路:
规则:
如果遇到正常结点3,则在当前字符串末尾添加 “3!”,“!”代表结束
如果遇到null,则在当前字符串末尾添加 “#!”,“#”表示null
序列化:使用递归先序遍历法遍历树,按照规则更新字符串,返回每个结点的字符串
反序列化:根据字符串生成一棵树。
1.将字符串根据“!”隔成一个个的数,注意添加至队列queue里
2.用递归先序遍历模板生成一棵树,参数为queue。
3.每层递归从queue出队一个数,递归停止条件为:碰到“#”(即null)。
为当前不为null的数创建结点。
queue中剩下的结点用作创建该结点的左子树,左边完了该右边了,queue中剩下的结点用作创建该结点的右子树。
每层递归完成后return当前结点。
代码:
public String res="";
String Serialize(TreeNode root) {
if(root==null)
return res=res+"#"+"!";
else
res=res+root.val+"!";
Serialize(root.left);
Serialize(root.right);
return res;
}
TreeNode Deserialize(String str) {
String[] strArray=str.split("!");
Queue<String> queue=new LinkedList<String>();
for(int i=0;i<strArray.length;i++)
queue.add(strArray[i]);
TreeNode head=buildTree(queue);
return head;
}
TreeNode buildTree(Queue<String> queue){
String cur=queue.poll();
if(cur.equals("#"))
return null;
TreeNode curNode=new TreeNode(Integer.parseInt(cur));
curNode.left=buildTree(queue);
curNode.right=buildTree(queue);
return curNode;
}
unit 3 Q8:莫里斯二叉树遍历法
要求时间复杂度O(N),空间复杂度O(1)
time:2019/08/03
思路:
Morris Traversal 很好的博客参考
注:Morris遍历法中,中序和前序仅在输出cur的时机有变化,其他相同。
这题挺难的(将满星),目前(2019/08/04)仅摸清楚它怎么做,具体机理留待研究。
代码:U3Q8_MorrisOrder.java
unit 3 Q9:在二叉树中,找到一条最长的路径,该路径上点的累加和为给定值k
要求:
路径:从每个结点往下,每次最多选一个子结点形成的结点链。
时间复杂度O(n),额外空间复杂度(h)
time:2019/08/05
思路:
递归前序遍历模板。
int preOrder(BNode T,int k,int preSum,int level,int maxLen,HashMap<Integer,Integer> sumMap)其中
k:题目给定值,路径上点的sum应等于k
preSum:根结点到该结点的父节点的Sum
level:当前结点的深度
maxLen:运行到现在时,累加和为k的最长路径长度
sumMap:HashMap结构,其(key,value)等于(每个结点的sum,结点对应的深度level)
1.计算根节点到当前结点的sum=根节点到父节点的sum+当前结点的值
2.如果当前结点的sum未在HashMap中出现,则将(当前节点的sum,深度level)加入sumMap
3.如果(当前结点的sum-任一上面结点的sum)==k,说明找到了一条累加和为k的路径:
计算(当前深度level-上面结点的level)即是该路径的长度;如果长度大于maxLen,则更新maxLen
4.递归遍历左子树;递归遍历右子树
5.最后return之前,由于该结点左、右子树都遍历过了,回去之前需要删除本层存在sumMap中的记录,以免回去后遍历右兄弟时,受左半边的值影响。
代码:U3Q9_MaxPathLenOf_SumEqualsK.java
unit 3 Q10:找二叉树中的最大二叉排序子树
要求:时间复杂度O(N),空间复杂度O(h)
time:2019/08/06
思路:
使用递归后序遍历模板,每轮返回自定义的NodeInfo对象
NodeInfo对象包含的四个参数(BNode subBST_Head,int subBST_Size,int subBST_Min,int subBST_Max)
为四项基本信息(T的最大子BST的头结点,T的最大子BST的大小,T的最大子BST的最小值,T的最大子BST的最大值)
1.如果碰到空,返回(null,0,Integer.MAX_VALUE,MIN_VALUE)
如果碰到叶子结点,则T就是BST,返回(T,1,T.value,T.value)
2.后序遍历左子树;后序遍历右子树
3.visit(T)如下:
当且仅当满足以下条件时T是最大二叉排序树:
- T左边的最大子BST以T.lchild为头结点,且左边的子BST最大值小于T.value
- T右边的最大子BST以T.rchild为头结点,且右边的子BST最小值大于T.value
返回【T,左BST大小+右BST大小+1,Min(左BST最小值, T.value),Max(右BST最大值, T.value)】
否则T不是BST,返回左b边子BST和右边子BST中较大者的四项基本信息
代码:U3Q10_MaxSubBST.java
unit 3 Q11:找到二叉树中的最大BST拓扑,返回最大BST拓扑的节点数
time:2019/08/08
规则:如果一个子孙结点比head结点小,那么从head结点开始,按二叉检索的方式向左出发,如果能走到该子孙结点,则说明该子孙节点符合head结点为头的二叉拓扑的一部分
如果一个子孙结点比head结点大,那么从head结点开始,按二叉检索的方式向右出发,如果能走到该子孙结点,则说明该子孙节点符合head结点为头的二叉拓扑的一部分
思路:
时间复杂度O(
N
2
N^2
N2)的方法
前序遍历每个结点,对每个结点求:若该结点为头结点,其最大BST拓扑的节点数。
遍历结束后,取所有结点最大BST节点数中最大者,即为该二叉树最大BST拓扑的节点数
Q:求以T为头结点的最大BST拓扑的节点数。
A:以层次遍历为模板,每次遍历取出队头元素。
如果队头元素按照二叉搜索的方法能找到,则说明队头结点符合T结点为头的二叉拓扑结构:
count计数器累加;左子树不空则左子树入队;右子树不空则右子树入队。
如果队头元素按二叉搜索的方法找不到,说明队头结点不符合二叉拓扑结构,其左、右结点自然不该入队
Q:判断该元素是否能按二叉搜索的方法找到。
A:递归二叉排序树遍历为模板,稍加修改即可。
每对一个结点找一次最大拓扑,就要将所有结点遍历一遍,O(N);对所有结点都要找一次以取最大值,O(N)。总的时间复杂度为O( N 2 N^2 N2)
代码:U3Q11_MaxBSTTopo.java
unit 2 Q12:二叉树ZigZag打印
剑指offer 33 牛客链接
time:2019/08/09
思路:
二叉树的层次遍历模板。
把队列换成双端队列;置lr表明方向。
从左至右(lr=true)时尾进头出,入队先入左结点再入右结点;(与层次遍历一致)
从右至左(lr=false)时头进尾出,入队先入右结点再入左结点。(全部反过来)
代码:
public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
ArrayList<ArrayList<Integer>> res=new ArrayList<ArrayList<Integer>>();
if(pRoot==null)
return res;
LinkedList<TreeNode> deque=new LinkedList<TreeNode>();//双端队列
boolean lr=true;//是否从左至右
TreeNode p=pRoot;
deque.addLast(p);
while(!deque.isEmpty()){
int count=0,layer_len=deque.size();
ArrayList<Integer> layer_res=new ArrayList<Integer>();
while(count<layer_len){
//从左到右,则与层次遍历一致,尾进头出,先入左再入右
if(lr){
p=deque.pollFirst();//头出
count++;
layer_res.add(p.val);
if(p.left!=null)
deque.addLast(p.left);
if(p.right!=null)
deque.addLast(p.right);
}else{
//从右至左,完全相反,即头进尾出,先入右再入左
p=deque.pollLast();//尾出
count++;
layer_res.add(p.val);
if(p.right!=null)
deque.addFirst(p.right);
if(p.left!=null)
deque.addFirst(p.left);
}
}
lr=!lr;//每层之后改变方向
res.add(layer_res);
}
return res;
}
unit 3 Q13:BST中有两个结点调换了位置。找出这两个结点并恢复原BST
time:2019/08/10
思路:
考虑中序遍历 1 2 3 4 5的BST的两种错误情况:
错误情况1:中序遍历为 5 2 4 3 1 的错误BST,5和1调换了位置。
中序遍历该二叉树会出现两次失序状况,第一次是5->2,第二次是4->1。可得第一次失序时,前面的5是出错的结点;第二次失序时,后面的1是出错结点。
错误情况2:中序遍历为1 2 4 3 5 的错误BST,3和4调换了位置。
由于调换的两个节点紧邻,所以中序遍历该二叉树只出现一次失序。
实现:
1.改中序遍历的模板,记录上一个visit()到的结点;
2.置一长度为2的错误结点数组errBNodes[]
如果进来时errBNodes[0]为空,说明之前还没出过错。把pre给errBNodes[0],当前的p给errBNodes[1]
- Q:Why 当前的p赋给errBNodes[1]?
A:如果两错误结点紧邻,只有一次失序,则刚好errBNodes[1]存的是出错结点中的后者;
如果不紧邻有两次失序,再更新errBNodes[1]也不迟。
如果进来时errBNodes[0]不为空,说明之前错过一次,这次是第二次失序,把p给errBNodes[1]
3.中序遍历后交换两个结点的值即可
代码:U3Q13_adjustBST_SwichedNode.java
unit 3 Q14:判断T1是否包含T2全部的拓扑结构
time:2019/08/11
思路:
设置两个二叉树递归遍历
check(head,T2)用来检查 是否head.value与T2.value且head树包含T2全部的拓扑结构
- 依次前序遍历T2,head跟着T2走;
- 如果T2中当前结点走到null,说明这一路如今走到头都成功了,返回true;
- 如果T2中当前结点.value不等于T2.value,失配,返回false;
- 如果T2中当前结点.value等于T2.value,只能说明当前这个节点匹配成功。
递归遍历左、右子树,左、右子树都返回true上来,当前结点以下都匹配成功才成功,返回true。
contains(T1,T2)用来求T1是否包含T2的全部拓扑结构
- 依次前序遍历T1,T2不动;
- 每遍历T1的一个结点,visit()为:用check()函数检查是否 该结点.value==T2.value且该结点的树中包含T2全部的拓扑结构,如果包含停止递归,返回true;
如果不包含,递归遍历左子树、右子树,左、右子树中有一支包含则该数包含,返回true
date:2020/02/10 更新:
剑指offer 26 是否包含树的子结构 牛客链接
代码:
public boolean HasSubtree(TreeNode T1,TreeNode T2) {
//T1为空:到底了都没有找到,不包含。
//T2为空:子树压根就没有,出错。
if(T1==null||T2==null)
return false;
if(check(T1,T2))
return true;
//T1,T2没匹配成功,看后面的了
boolean isLeftContain=HasSubtree(T1.left,T2);
boolean isRightContain=HasSubtree(T1.right,T2);
return isLeftContain||isRightContain;
}
boolean check(TreeNode head,TreeNode T2){
//遍历到空,不管有没有都无所谓
if(T2==null)
return true;
//T2不空,左边的空了,肯定失配了
if(head==null)
return false;
//都不空,如果遍历到的不一致,那也是失配了
if(T2.val!=head.val)
return false;
boolean isLeftMatch=check(head.left,T2.left);
boolean isRightMatch=check(head.right,T2.right);
return isLeftMatch && isRightMatch;//左右两边都匹配才行
}
unit 3 Q15:判断T1树中是否包含与T2完全相同的子树
time:2019/08/12
思路:
跟Q14思路基本相同。
isTreeEqual(T1,T2)用后续递归思想,判断两棵树是否相同;
isT1ContainT2(T1,T2)遍历T1,对每个结点调用isTreeEqual()。
左右两边有一个相同,则T1中包含与T2完全相同的子树,返回true。
代码:U3Q15_isT1ContainT2.java
unit 3 Q16:判断二叉树是否是平衡二叉树
time:2019/08/14
思路:
使用后序遍历模板。每层遍历返回一个NodeInfo对象,每个对象包含(int level,boolean isBalanced)两个参数:level为返回结点的最大高度,isBalanced为返回结点是否是平衡二叉树。
每次对左右子树返回上来的NodeInfo做判断:
如果左、右子树都是平衡二叉树,且(左子树高度-右子树高度)的绝对值小于等于1,则当前结点是平衡二叉树,返回(左右子树高度较大者+1,true);
否则返回(左右子树高度较大者+1,false)。
代码:U3Q16_isBalanced.java
剑指offer 55_2 牛客链接
date:2020/02/20
思路:
用递归求二叉树深度的方法,在返回高度时判断当前树是否是BST,即是否[左子树高度-右子树高度|<=1。如果 左子树、右子树、当前树 不是BST,返回-1,反之返回当前树的高度。
public class Solution {
public boolean isBalanced=true;
public boolean IsBalanced_Solution(TreeNode root) {
getHeight(root);
return isBalanced;
}
public int getHeight(TreeNode root){
if(root==null)
return 0;
int leftH=getHeight(root.left);
int rightH=getHeight(root.right);
if(Math.abs(leftH-rightH)>1)
isBalanced=false;
int height=Math.max(leftH,rightH)+1;
return height;
}
}
unit 3 Q17:根据后序数组重建二叉排序树
time:2019/08/15
总体思路:
先用一次postOrder_judge()判断该后序数组合不合法,如果合法用postOrder_build()根据arr构造BST
实现:
boolean postOrder_judge(int[] arr,int start,int end)
如果arr[]是二叉排序树的后序遍历结果,则arr的最后一个结点必定是头结点;
其余的N-1个点会分成左右两拨,左边的是左子树的结点都比头节点小,右边的是右子树的结点都比头结点大。
如果以上不满足,则一定不是二叉排序树。
1.递归中止条件:如果start>=end,则返回true。
2.置leftLast初值为start,指向左子树在后序遍历中最后一个结点;
RightFirst初值为start+1,指向右子树在后序遍历中的第一个结点。
for循环向后找,每次都更新leftLast为i,rightFirst为i+1。找到当前值比最后一个还大的时候停止。
3.如果右边有比最后一个还小的,不符合BST后序遍历性质,直接返回false;
4.走完以上说明当前节点暂时符合BST后序遍历。
将(arr,start,leftLast)作为参数递归遍历左子树、将(arr,rightStart,end-1)作为参数递归遍历右子树。
如果左右子树都返回的true说明左右子树也符合,该节点返回true,反之返回false
//2020/02/13 更新:
剑指offer 33 牛客链接
代码:
public boolean VerifySquenceOfBST(int [] sequence) {
if(sequence==null||sequence.length==0)
return false;
return postOrder_judge(sequence,0,sequence.length-1);
}
private boolean postOrder_judge(int[] arr,int start,int end){
if(start>=end)
return true;
int headNum=arr[end];
int leftLast=start,rightFirst=start+1;
//找到这一轮左右子树的分界点,即为leftLast,rightFirst=leftNum+1
for(int i=start;i<end;++i){
if(arr[i]>headNum)
break;
leftLast=i;
rightFirst=i+1;
}
//验证左右子树是否符合后序数组的规则,
//即左子树的所有结点都要比headNum小,右子树的所有结点都要比headNum大
//Q:为啥不判断左子树?A:有可能左子树为空,但是leftLast最小就是start。
//而且左边本来就是得比headNum小,如果大就退出for循环了。所以只用判断右边即可
for(int i=rightFirst;i<end;++i)
if(arr[i]<headNum)
return false;
//如果能运行到这,说明以arr[end]为头结点,左右子树暂时符合要求。
//进入更深层遍历
boolean isLeft=postOrder_judge(arr,start,leftLast);
boolean isRight=postOrder_judge(arr,rightFirst,end-1);
return isLeft&&isRight;
}
BNode postOrder_build(int[] arr,int start,int end)
和判断的后序遍历模板相似。建树需返回根节点
1.递归中止条件:如果start>end则返回Null;如果start==end,叶子结点,则建个叶子结点返回之。
2.找到leftLast、rightFirst的位置,同判断的第2步。
3.已经判断过所以不用判断,生成即可。生成当前所对应的结点head。
4.递归遍历左子树,返回的左子树根节点作为head的左儿子;递归遍历右子树,返回的右子树根节点作为head的有儿子。最后返回head
代码:U3Q17_buildBSTByArr.java
unit 3 Q18:判断一棵树是否为二叉排序树
time:2019/08/16
思路:
稍微改一下二叉树中序非递归模板即可。
记录上一个遍历到的结点为pre,在visit(T)的位置判断:如果pre大于p,返回false;否则全部遍历之后返回true
代码:U3Q18_isBST.java
unit 3 Q19:判断一棵树是否为完全二叉树
time:2019/08/16
思路:
unit 3 Q19:判断一棵树是否为完全二叉树
思路:
王道上更简单的方法(2019/08/28修改)
- 模仿层次遍历。预先设置空结点NullNode(java的队列不能入队null结点,用NullNode代替之)
- 如果当前节点不是NullNode,将左、右孩子入队。注意,入队时左右孩子不为空则将其入队,如果为空则将NullNode入队。
- 否则,当前结点是NullNode,则NullNode第一次出现,如果是完全二叉树,之后所有结点都必须是NullNode。换言之,出现了第一次NullNode之后,再开一个while循环——队不空即循环,如果碰到不是NullNode就返回true。若安然遍历完,在最外层循环外返回false。
代码:U3Q19_isComplete.java
unit 3 Q20:根据中序遍历序列生成平衡BST
time:2019/08/21
思路:
二叉树前序递归遍历模板。
用数组中最中间的数生成当前结点,
将中间数左边的数去生成当前结点的左子树,将中间数右边的数去生成当前结点的右子树。
测试略。
代码:U3Q20_generateBalancedBST.java
unit 3 Q21:在含有父节点指针的二叉树中,给定一结点,找到中序遍历下该节点的后继结点
剑指offer 8
date:2019/08/22
思路:
先通过其特殊的父节点指针一路向上,找到二叉树的头结点。
对头结点进行递归中序遍历,设置队列记录遍历顺序。
从顺序队列中找到给定结点的后续结点。
测试略。
public ParentBNode getNext(ParentBNode myNode){
ParentBNode head;
ParentBNode p=myNode;
//找到二叉树的头结点
while(p.parent!=null)
p=p.parent;
head=p;
//从头结点开始,递归中序遍历,用队列记录顺序
Queue<ParentBNode> InQueue=new ArrayDeque<>();
InOrder(head,InQueue);
//在队列中找到后续结点的位置
while(!InQueue.isEmpty()){
p=InQueue.poll();
if(p==myNode)
return InQueue.poll();
}
return null;
}
void InOrder(ParentBNode T, Queue<ParentBNode> queue){
if(T!=null){
InOrder(T.lchild,queue);
queue.add(T);
InOrder(T.rchild,queue);
}
}
date:2020/05/10更新:不用队列存,牛客通过
public class Solution {
TreeLinkNode pre=null;//上一个节点
TreeLinkNode pNode=null;
TreeLinkNode res=null;
public TreeLinkNode GetNext(TreeLinkNode pNode)
{
this.pNode=pNode;
TreeLinkNode root=pNode;
TreeLinkNode cur=pNode;
//先找到根节点
while(cur.next!=null){
root=cur.next;
cur=cur.next;
}
//现在根节点就是root
//中序遍历找下一个结点,存在res里
inOrder(root);
return res;
}
//中序遍历
void inOrder(TreeLinkNode T){
if(T!=null){
inOrder(T.left);
//上一个是pNode,当前就res了
if(pre==pNode)
res=T;
pre=T;
inOrder(T.right);
}
}
}
unit 3 Q22:二叉树中找到两个结点的最近公共祖先
time:2019/08/23 Leetcode 难度:简单
思路:
左右子树分别向上传各自找到的给定结点,如果两边都找到了,就改成上传当前结点(最近公共祖先);
否则接着上传各自找到的给定结点,直到找到两边都p1或P2的情况,改成上传当前结点。
实现:
二叉树递归后序遍历模板。
1.递归中止条件:
碰到null,返回null。
碰到p1结点或者P2结点,则返回该结点。
2.依次遍历左子树、右子树。
3.
(1)如果两边都返回上来空,则左、右子树都没找到p1和p2结点,接着返回null
(2)如果两边都不为空,说明左边发现过p1或P2,右边也发现过p1或p2,当前结点是p1和p2一路向上第一次相交的结点。返回当前节点
(3)如果一边为空一边不为空,则说明只有一边找到p1或者p2。假设不空的那边返回上来结点node,
node只有两种情况,一种是返回p1和P2中的一个,一种是找到的最小公共祖先。
不论哪种情况,只需要将node即不为空一边返回的结点传上去即可。
(第一种情况,返回P1和p2中的一个,则说明之前没有找到公共结点,后面cur节点的某一个祖先结点一定会出现两边不为空的情况,该祖先结点便是最小公共祖先;
第二种情况,已经找到了最近公共祖先,当前结点的祖先结点不会再两边不为空了,一路传回去即可)
代码:
class Solution {
public TreeNode lowestCommonAncestor(TreeNode T,
TreeNode p, TreeNode q) {
if(T==null)
return T;
if(T==p||T==q)
return T;//找到了,返回之
TreeNode leftUp=lowestCommonAncestor(T.left,p,q);
TreeNode rightUp=lowestCommonAncestor(T.right,p,q);
//都为空,都没找到,返回空
if(leftUp==null&&rightUp==null)
return null;
//只有一个为空,谁不空返回谁
if(leftUp==null||rightUp==null)
return leftUp!=null?leftUp:rightUp;
//都不空。则T就是公共祖先,返回之
return T;
}
}
;
unit 3 Q22_2:BST中找到两个结点的最近公共祖先
剑指offer 67_2 Leetcode 简单
date:2020/02/26
思路:
递归遍历(但不回溯,一条路下去就找到了,一路返回上来即可)
BST的特点:T的左子树结点都比T小,T的右子树结点都比T大。
每次如果T比p,q都小,则T向右找;反之T比p,q都大,向左找。
上述两种情况都不是,则一大一小,T就是要找到的最近公共祖先。
代码:
class Solution {
public TreeNode lowestCommonAncestor(TreeNode T, TreeNode p, TreeNode q) {
if(T==null)
return T;//按理说不会找到叶子结点下面的结点null的。如果到了就是说没找到
//T太小去右边找
if(T.val<p.val&&T.val<q.val)
return lowestCommonAncestor(T.right,p,q);
//T太大了去右边找
if(T.val>p.val&&T.val>q.val)
return lowestCommonAncestor(T.left,p,q);
//一大一小,就是这个点,一路传回去
return T;
}
}
unit 3 Q23:求二叉树中节点间的最大距离
从A结点出发走到B,每个结点最多只能经过一次,该路径上的结点数叫做A到B的距离。
要求:时间复杂度O(N)
time:2019/08/24
思路:
二叉树后序遍历模板,每次遍历返回BNodeInfo(curMax,maxHigh)对象,装该点的(最大路径长度,该点的高度)
每个结点的最大距离无非两种情况:
- 不跨当前结点的最大距离:(左子树最大路径长度 和 右子树最大路径长度 中较大者)
- 跨当前结点的最大距离:(左子树高度+右子树高度+1)
每轮后序递归,需要计算(1)、(2)两者,取其较大者为当前结点最大距离。
由于计算(2)需要知道左右子树的高度,所以需要将每个结点的高度作为BNodeInfo对象的第二个参数,传回上一层,方便计算(2)即当前节点的最大距离。
实现:
1.后序递归终止条件:遍历到空,返回(0,0)
2.递归遍历左、右子树,获得左右子树的二元信息:BNodeInfo对象leftInfo、rightInfo
3.通过leftInfo,rightInfo获得左右子树的最大长度、高度
4.计算 不跨当前节点的最大距离(左、右子树路径长度较大者) 和 跨当前结点的最大距离(左子树高度+右子树高度+1),再取其大者作为当前结点的最大距离curMax。
取(左右子树高度较大者+1)为当前结点的高度curHigh。
5.返回当前结点信息(curMax,curHigh)
Finally,取T结点的curMax为最大距离即可。
代码:U3Q23_maxDistance.java
unit 3 Q24:使用先序、中序数组构建一棵二叉树
剑指offer 7 Leetcode 105 难度:中等
date:2019/08/26
思路:
使用二叉树递归遍历,递归中止条件是左子树先序区间不成立了。
每次取先序遍历小区间内的第一个结点,创建该结点。
将(总先序序列,左子树的先序区间,总中序序列,左子树的中序区间,map)传入下一层递归,构建该结点左子树。
将(总先序序列,右子树的先序区间,总中序序列,右子树的中序区间,map)传入下一层递归,构建该结点右子树。
左右子树构造完,返回该结点。
详细思路如图所示:
代码:U3Q24_buildByPreIn.java
public BNode preInToTree(int[] pre,int[] in){
if(pre==null||in==null)
return null;
//初始化HashMap map
HashMap<Integer,Integer> map=new HashMap<Integer, Integer>();
for(int i=0;i<in.length;i++)
map.put(in[i],i);
return preIn(pre,0,pre.length-1,in,0,in.length-1,map);
}
private BNode preIn(int[] p,int pi,int pj,int[] n,int ni,int nj,
HashMap<Integer,Integer> map){
if(pi>pj)
return null;
BNode curNode=new BNode(p[pi]);//构建当前结点
int index=map.get(p[pi]);//获得当前结点在中序中的位置
curNode.lchild=preIn(p,pi+1,pi+index-ni,n,ni,index-1,map);//构建左子树
curNode.rchild=preIn(p,pi+index-ni+1,pj,n,index+1,nj,map);//构建右子树
return curNode;
}
unit 3 Q25:使用中序、后序数组构造一棵二叉树
time:2019/08/27
思路:
跟上一题的思路相似。只不过用先序和中序构造时,取先序小区间内第一个构造结点;
在中序和后序构造时,选后序小区间内的最后一个构造结点。
将(总中序序列,左子树的中序区间,总后序序列,左子树的后序区间,map)传入下一层递归,构建该结点左子树。
将(总中序序列,右子树的中序区间,总后序序列,右子树的后序区间,map)传入下一层递归,构建该结点右子树。
左右子树构造完,返回该结点。
详细思路如图所示:
代码:U3Q25_buildByInPost.java
unit 3 Q26:给定一有序中序序列,统计可生成多少棵不同的二叉树
time:2019/08/28
思路:
若中序遍历有序且无重复,则必为二叉排序树。
设num[a]:a个结点的二叉排序树有几种可能。num[a]可以由前(n-1)个{num[1],num[2],…num[n-1]}求得。
假设我想求N个结点的BST有几种可能。当前序列为{1,2,3,…,N-1,N},置num[0]=1
- 如果以1为头结点:1没有左子树;1的可能状况数=1右子树的可能状况数 = num[0]*num[N-1]
- 如果以2为头结点:2左子树1个结点,可能状况数num[1];
2右子树(N-2)个结点,可能状况数num[N-2];
总可能状况数=左子树可能数*右子树可能数 = num[1]*num[n-2] - 如果以3为头结点:3左子树2个结点,可能状况数num[2];
3右子树(N-3)个结点,可能状况数num[N-3];
总可能状况数=左子树可能数*右子树可能数 = num[2]*num[N-3]
… - 如果以N为头结点,N没有右子树,该情况的可能状况数=N的右子树可能状况数 = num[N-1]*num[0]
把以上所有可能状况数都加起来,就是N个结点的BST有多少钟可能状况。
即:num[N] = num[0]*num[n-1] + num[1]*num[n-2] +…+num[n-1]*num[0]
由上述过程可知,想要求num[N],需要将之前的num[1]、num[2]、…num[N-1]都要求出来
代码:U3Q26_InToNumTree.java
unit 3 Q27:给定一有序中序序列{1,2,…,N},生成所有可能不同的BST
time:2019/08/29
思路:
上一题的延伸版本,Q27要求出有多少种可能,本题要求把这些可能都生成出来。
List< BNode> generate(int start,int end):
生成中序序列{start,start+1,start+2,…,end-1,end}的每一棵树,返回装满各树根结点的List的rootList;
1.设置List< BNode>;从start开始end结束,依次遍历各结点;
2.遍历过程中,每个结点都当一次头,使用递归generate(start,i-1)生成左子树的集合leftTrees,generate(i+1,end)生成右子树的集合rightTrees;
3.设两层for循环,为左子树和左子树中的每一棵可能树都两两结合,连在当前结点的左右。
调用cloneTree(head)复制该树为newTree,加入rootList;
4.全部遍历后返回head。
cloneTree(BNode T):
原模原样复制一棵树。
前序递归模板。照着原样,每次建立一个结点。最后返回head即可。
代码:U3Q27_InToAllBST.java
unit 3 Q28:二叉树的镜像
操作给定的二叉树,将其变换为源二叉树的镜像。
剑指offer 27 牛客链接
date:2020/02/10
思想:二叉树后序遍历;visit()中交换左右子树即可。
代码:
public void Mirror(TreeNode root) {
if(root!=null){
Mirror(root.left);
Mirror(root.right);
TreeNode temp=root.left;
root.left=root.right;
root.right=temp;
}
}
unit 3 Q29:判断二叉树是否对称
请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
剑指offer 28 牛客链接 Leetcode 101 难度:简单
date:2020/02/11
思想:
递归遍历的时候吃两边。第一颗树先左再右,第二棵树先右再左,每轮比较结点是否一致。
boolean isSymmetrical(TreeNode pRoot){
return isSymmetrical(pRoot,pRoot);
}
boolean isSymmetrical(TreeNode root1,TreeNode root2){
if(root1==null&&root2==null)
return true;//都遍历到头了,当然true
if(root1==null||root2==null)
return false;//只有一边null,不对称
if(root1.val!=root2.val)
return false;//两边不一样,不对称
boolean isLeft=isSymmetrical(root1.left,root2.right);
boolean isRight=isSymmetrical(root1.right,root2.left);
return isLeft&&isRight;
}
unit 3 Q30:二叉树中和为给定值的路径
剑指offer 34 牛客链接
date:2020/02/13
题目:输入一颗二叉树的根节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。
路径定义:从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。
//和本章Q9有点相似,两题的路径定义不同,要求略有不同。
//我觉得这道题的解法更好一点。
代码:
ArrayList<ArrayList<Integer>> res=new ArrayList<ArrayList<Integer>>();
ArrayList<Integer> list=new ArrayList<Integer>();//当前list
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
if(root==null||target<=0)
return res;
list.add(root.val);//当前结点加入当前List
target=target-root.val;//减掉当前节点的值,还剩多少要满足
if(target==0&&root.left==null&&root.right==null)
res.add(new ArrayList<Integer>(list));//重新new一个list加入
FindPath(root.left,target);
FindPath(root.right,target);
//当前结点遍历完了,该退回上一层递归了。退回之前把自己删了
list.remove(list.size()-1);
return res;//平时用不到,就最上面一层才用得到
}
unit 3 Q31:二叉搜索树的第k个结点
剑指offer 54 牛客链接
给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。
date:2020/02/20
思路:
中序遍历的简单改造,练手题。
代码:
public class Solution {
int count=0;
TreeNode res;
TreeNode KthNode(TreeNode pRoot, int k){
inOrder(pRoot,k);
return res;
}
void inOrder(TreeNode T,int k){
if(T!=null){
inOrder(T.left,k);
if(++count==k)
res=T;
inOrder(T.right,k);
}
}
}
//非递归版
int count=0;
TreeNode KthNode(TreeNode pRoot, int k)
{
if(pRoot==null)
return null;
Stack<TreeNode> stack=new Stack<TreeNode>();
TreeNode p=pRoot;
while(p!=null||!stack.isEmpty()){
if(p!=null){
stack.push(p);
p=p.left;
}else{
p=stack.pop();
if(++count==k)
return p;
p=p.right;
}
}
return null;
}