Unit 3 树和二叉树

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,非递归方法,使用栈辅助,与层次遍历很像。(层次遍历用队列,先序遍历用栈)

  1. 先入根
  2. 弹出栈顶元素,访问之。将其右孩子、左孩子依次入栈
  3. 栈不空,循环

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)
U3 Q6图例
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
U3 Q7图示
序列化:使用递归先序遍历法遍历树,按照规则更新字符串,返回每个结点的字符串

反序列化:根据字符串生成一棵树。
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 很好的博客参考
InOrder图示

在这里插入图片描述
注:Morris遍历法中,中序和前序仅在输出cur的时机有变化,其他相同。
在这里插入图片描述
这题挺难的(将满星),目前(2019/08/04)仅摸清楚它怎么做,具体机理留待研究。

代码:U3Q8_MorrisOrder.java

 

unit 3 Q9:在二叉树中,找到一条最长的路径,该路径上点的累加和为给定值k

要求:
路径:从每个结点往下,每次最多选一个子结点形成的结点链。
时间复杂度O(n),额外空间复杂度(h)
time:2019/08/05
Q9图示

思路:
递归前序遍历模板。
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)
Q10图例
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结点为头的二叉拓扑的一部分
Q11图例
思路:
时间复杂度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打印

Q12:图示
剑指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全部的拓扑结构

Q14图示
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. 不跨当前结点的最大距离:(左子树最大路径长度 和 右子树最大路径长度 中较大者)
  2. 跨当前结点的最大距离:(左子树高度+右子树高度+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;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值