• (最新版2022版)剑指offer之树和二叉树题解



    1、《剑指offer》JZ7 重建二叉树

    题目描述:

    输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回根结点。

    解题思路:

    树的遍历有三种:分别是前序遍历、中序遍历、后序遍历。本题是根据前序和中序遍历序列重建二叉树,我们可以通过一个具体的实例来发现规律,不难发现:前序遍历序列的第一个数字就是树的根结点。在中序遍历序列中,可以扫描找到根结点的值,则左子树的结点都位于根结点的左边,右子树的结点都位于根结点的右边。

    这样,我们就通过这两个序列找到了树的根结点、左子树结点和右子树结点,接下来左右子树的构建可以进一步通过递归来实现。

    举例:
    在这里插入图片描述
    编程实现(Java):

    import java.util.*;
    /**
     * Definition for binary tree
     * public class TreeNode {
     *     int val;
     *     TreeNode left;
     *     TreeNode right;
     *     TreeNode(int x) { val = x; }
     * }
     */
    public class Solution {
        HashMap<Integer,Integer> map = new HashMap<>();
        public TreeNode reConstructBinaryTree(int [] pre,int [] vin) {
            if(pre == null){
                return null;
            }
    
            for(int i = 0; i < vin.length; i++){
                map.put(vin[i], i);
            }
    
            return f(pre, 0, pre.length - 1, vin, 0, vin.length - 1);
        }
    
        public TreeNode f(int[] pre, int l1, int r1, int[] vin, int l2, int r2){
            if(l1 > r1){
                return null;
            }
    
            TreeNode root = new TreeNode(pre[l1]);
            int i = map.get(pre[l1]);
    
            root.left = f(pre, l1 + 1, l1 + i - l2, vin, l2, i - 1);
            root.right = f(pre, l1 + i - l2 + 1, r1, vin, i + 1, r2);
            return root;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    2、《剑指offer》JZ26树的子结构

    题目描述:

    输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)

    解题思路:

    要查找树A中是否存在和树B结构一样的子树,我们可以分为两步:第一步,在树A中找到和树B的根结点值一样的结点R;第二步,判断树A中以R为根结点的子树是不是包含和树B一样的结构。

    对于这两步,第一步实际上就是树的遍历,第二步是判断是否有相同的结构,这两步都可以通过递归来实现。

    举例:
    在这里插入图片描述
    编程实现(Java):

    /**
    public class TreeNode {
        int val = 0;
        TreeNode left = null;
        TreeNode right = null;
    
        public TreeNode(int val) {
            this.val = val;
    
        }
    
    }
    */
    public class Solution {
        public boolean HasSubtree(TreeNode root1,TreeNode root2) {
            if(root1 == null || root2 == null){
                return false;
            }
    
            if(isSub(root1, root2)){
                return true;
            }else{
                return HasSubtree(root1.left, root2) || HasSubtree(root1.right, root2);
            }
        }
    
        public boolean isSub(TreeNode root1, TreeNode root2){
            if(root2 == null){
                return true;
            }
    
            if(root1 == null){
                return false;
            }
    
            if(root1.val != root2.val){
                return false;
            }else{
                return isSub(root1.left, root2.left) && isSub(root1.right, root2.right);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    3、《剑指offer》JZ27二叉树的镜像

    题目描述:

    操作给定的二叉树,将其变换为原二叉树的镜像。

    解题思路:

    求一棵树的镜像的过程:先前序遍历这棵树的每个结点,如果遍历到的结点有子结点,就交换它的两个子结点。当交换完所有的非叶结点的左、右子结点后,就可以得到该树的镜像。

    如下面的例子,先交换根节点的两个子结点之后,我们注意到值为10、6的结点的子结点仍然保持不变,因此我们还需要交换这两个结点的左右子结点。做完这两次交换之后,我们已经遍历完所有的非叶结点。此时变换之后的树刚好就是原始树的镜像。

    举例:
    在这里插入图片描述
    在这里插入图片描述
    编程实现(Java):

    import java.util.*;
    
    /*
     * public class TreeNode {
     *   int val = 0;
     *   TreeNode left = null;
     *   TreeNode right = null;
     *   public TreeNode(int val) {
     *     this.val = val;
     *   }
     * }
     */
    
    public class Solution {
        /**
         * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
         *
         * 
         * @param pRoot TreeNode类 
         * @return TreeNode类
         */
        public TreeNode Mirror (TreeNode pRoot) {
            // write code here
            if(pRoot == null){
                return null;
            }
    
            if(pRoot.left != null){
                Mirror(pRoot.left);
            }
            if(pRoot.right != null){
                Mirror(pRoot.right);
            }
    
            TreeNode temp = pRoot.left;
            pRoot.left = pRoot.right;
            pRoot.right = temp;
            return pRoot;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    4、《剑指offer》JZ32从上往下打印二叉树

    题目描述:

    从上往下打印出二叉树的每个节点,同层节点从左至右打印。

    解题思路:

    本题实际上就是二叉树的层次遍历,深度遍历可以用递归或者栈,而层次遍历很明显应该使用队列。同样我们可以通过一个例子来分析得到规律:每次打印一个结点时,如果该结点有子结点,则将子结点放到队列的末尾,接下来取出队列的头重复前面的打印动作,直到队列中所有的结点都打印完毕。

    举例:
    在这里插入图片描述
    编程实现(Java):

       public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
            //思路:使用队列来实现
            ArrayList<Integer> list=new ArrayList<>();
            if(root==null)return list;
            Queue<TreeNode> queue=new LinkedList<>(); 		//定义一个队列
            queue.add(root);
            while(queue.size()!=0){
                TreeNode temp=queue.poll();					//把队头元素丢到list中
                list.add(temp.val);
                if(temp.left!=null) queue.add(temp.left);  //左孩子不空,左孩子进队列
                if(temp.right!=null) queue.add(temp.right);//右孩子不空,右孩子进队列          
            }
            return list;
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    5、《剑指offer》JZ33 二叉搜索树的后序遍历序列

    题目描述:

    输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。

    解题思路:

    对于后续遍历序列,序列的最后一个值一定是树的根结点,而由二叉搜索树的性质:左小右大,我们可以从头开始遍历,当遍历到某个值比根结点大时停止,记为flag,此时flag之前的所有数值都是二叉搜索树的左子树的结点,flag以及flag之后的所有数都是二叉搜索树的右子树的结点。这是由二叉搜索树以及后序遍历共同决定的。

    接下来,我们就可以把任务交给递归,同样的方法去判断左子树和右子树是否是二叉搜索树,这显然是典型的递归解法。

    举例:

    以{5,7,6,9,11,10,8}为例,后序遍历结果的最后一个数字8就是根结点的值。在这个数组中,前3个数字5、7和6都比8小,是值为8的结点的左子树结点;后3个数字9、11和10都比8大,是值为8的结点的右子树结点。

    我们接下来用同样的方法确定与数组每一部分对应的子树的结构。这其实就是一个递归的过程。对于序列5、7、6,最后一个数字6是左子树的根结点的值。数字5比6小,是值为6的结点的左子结点,而7则是它的右子结点。同样,在序列9、11、10中,最后一个数字10是右子树的根结点,数字9比10小,是值为10的结点的左子结点,而11则是它的右子结点,所以它对应的二叉搜索树如下:
      在这里插入图片描述
    编程实现(Java):

    public class Solution {
        public boolean VerifySquenceOfBST(int [] sequence) {
            //if (sequence == null) {
            //    return false;
            //}
    
            if (sequence.length == 0) {
                return false;
            }
            
            return f(sequence, 0, sequence.length - 1);
        }
    
        public boolean f(int[] sequence, int l, int r) {
            if (l >= r) {
                return true;
            }
    
            int root = sequence[r];
            int first = l;
            while (root > sequence[first]) {
                first++;
            }
    
            for (int i = first; i < r; i ++) {
                if (root > sequence[i]) {
                    return false;
                }
            }
    
            return f(sequence, l, first - 1) && f(sequence, first, r - 1);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    6、《剑指offer》24寻找二叉树中和为某一值的路径(二)

    题目描述:

    输入一颗二叉树的根结点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)

    解题思路:

    本题实质上就是深度优先搜索。使用前序遍历的方式对整棵树进行遍历,当访问到某一个结点时,将该结点添加到路径上,并且累加该结点的值。当访问到的结点是叶结点时,如果路径中的结点值之和正好等于输入的整数,则说明是一条符合要求的路径。如果当前结点不是叶结点,则继续访问它的子结点。

    当找到一条符合要求的路径之后,需要回溯进一步查找别的路径,因此,这实际上仍然是一个递归的过程,特别注意在函数返回之前要删掉当前结点,从而才可以正确的回溯。

    举例:
    在这里插入图片描述
    编程实现(Java):

    import java.util.ArrayList;
    /**
    public class TreeNode {
        int val = 0;
        TreeNode left = null;
        TreeNode right = null;
    
        public TreeNode(int val) {
            this.val = val;
    
        }
    
    }
    */
    public class Solution {
        ArrayList<ArrayList<Integer>> res = new ArrayList<>();
        ArrayList<Integer> list = new ArrayList<>();
        
        public ArrayList<ArrayList<Integer>> FindPath(TreeNode root, int expectNumber) {
    
            if (root == null) {
                return res;
            }
    
            dfs(root, expectNumber);
            return res;
        }
    
        void dfs(TreeNode root, int target) {
            if (root == null) {
                return;
            }
    
            list.add(root.val);
            target -= root.val;
            if (root.left == null && root.right == null && target == 0) {
                res.add(new ArrayList<>(list));
            }
            dfs(root.left, target);
            dfs(root.right, target);
            list.remove(list.size() - 1);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    7、《剑指offer》JZ36二叉搜索树与双向链表

    题目描述:

    输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。

    解题思路:

    首先要理解此题目的含义,在双向链表中,每个结点都有前后两个指针;二叉树中,每个结点都有两个指向子结点的左右指针,同时,二叉搜索树树也是一种排序的数据结构。因此,从结构上看,双向链表的前后指针和二叉搜索树的左右指针结构相似,因此,可以实现互相之间的转换。

    首先,根据二叉搜索树的特点,左结点的值<根结点的值<右结点的值,据此不难发现,使用二叉树的中序遍历得到的数据序列就是递增的排序顺序。因此,首先确定应该采用中序遍历方法。

    接下来,可以根据下图,将树分为三个部分,值为10的根结点、根为6的左子树和根为14的右子树。不难看出以下规律:根据中序遍历的顺序,当我们遍历到根结点时,它的左子树已经转换为一个排好序的双向链表,并且链表最后一个结点是左子树值最大的结点,我们把这个值最大(8)的结点同根结点链接起来,10就成了最后一个结点,接着遍历右子树,将根结点同右子树中最小的结点链接起来。
    在这里插入图片描述
    编程实现(Java):

    /**
    public class TreeNode {
        int val = 0;
        TreeNode left = null;
        TreeNode right = null;
    
        public TreeNode(int val) {
            this.val = val;
    
        }
    
    }
    */
    import java.util.*;
    public class Solution {
        Queue<TreeNode> queue = new LinkedList<>();
        public TreeNode Convert(TreeNode pRootOfTree) {
            if (pRootOfTree == null) {
                return null;
            }
    
            inOrder(pRootOfTree);
            TreeNode head = queue.poll();
            TreeNode pre = head;
            while(!queue.isEmpty()){
                TreeNode next = queue.poll();
                pre.right = next;
                next.left = pre;
                pre = next;
            }
    
            return head;
        }
    
        public void inOrder(TreeNode root) {
            if (root == null) {
                return;
            }
    
            inOrder(root.left);
            queue.add(root);
            inOrder(root.right);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    8、《剑指offer》38计算二叉树的深度

    题目描述:

    输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。

    解题思路:

    本题相对比较简单。根据二叉树深度的定义,我们有以下理解:如果一棵树只有一个结点,那么它的深度为1。如果根结点只有左子树而没有右子树,那么树的深度为其左子树深度加1;相反,如果根结点只有右子树而没有左子树,那么深度为右子树深度加1;如果既有左子树又有右子树,那么该树的深度为左、右子树深度的较大值加1。

    因此,很明显本题应该使用递归的思路来解决。

    举例:
    在这里插入图片描述
    编程实现(Java):

    class Solution {
        public int maxDepth(TreeNode root) {
            if(root == null) return 0;
            return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1; //为什么要加1,是因为1代表根节点的深度
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    9、《剑指offer》JZ79判断是不是平衡二叉树

    题目描述:

    输入一棵二叉树,判断该二叉树是否是平衡二叉树。这里的定义是:如果某二叉树中任意结点的左、右子树的深度相差不超过1,那么它就是一棵平衡二叉树。

    解题思路:

    首先对于本题我们要正确理解,一般情况下,平衡二叉树就是AVL树,它首先是二叉搜索树(左小右大),其次满足左右子树高度之差不超过1。但是在本题中,没有二叉搜索树的要求,只对平衡与否进行判断即可。

    根据求二叉树深度的思路我们很容易想到一种解法,即:在遍历树的每一个结点时,求其左右子树的深度,判断深度之差,如果每个结点的左右子树深度相差都不超过1,那么就是一棵平衡二叉树。本思路直观简洁,但是需有很多结点需要重复遍历多次,时间效率不高。

    为了避免重复遍历,我们可以得到一种 每个结点只遍历一次的解法。 思路如下:采用后序遍历的方式遍历二叉树的每个结点,这样在遍历到每个结点的时候就已经访问了它的左右子树。所以,只要在遍历每个结点的时候记录它的深度,我们就可以一边遍历一边判断每个结点是不是平衡的。

    编程实现(Java):

    public class Solution {
        public boolean IsBalanced_Solution(TreeNode root) {
            if (root == null) {
                return true;
            }
    
            //判断根节点是不是平衡二叉树
            int left = getHeight(root.left);
            int right = getHeight(root.right);
            if (Math.abs(left - right) > 1) {
                return false;
            }
    
            //判断根节点的左右子树是不是平衡二叉树
            return IsBalanced_Solution(root.left) && IsBalanced_Solution(root.right);
        }
    
        public int getHeight(TreeNode root) {
            if (root == null) {
                return 0;
            }
    
            int left = getHeight(root.left);
            int right = getHeight(root.right);
            return Math.max(left, right) + 1;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    10、《剑指offer》JZ8二叉树的下一个结点

    题目描述:

    给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。

    解题思路:

    本题解决起来并不是很困难,主要是分析清楚所有可能的不同情况。对于中序遍历序列来说,遵循“左->根->右”的顺序,在深刻理解中序遍历的基础上,结合一些具体的实例我们不难得出以下结论。

    找一个结点在中序遍历序列中的下一个结点共有三种不同的情况:

    如果一个结点有右子树,那么它的下一个结点就是它的右子树中最左边的那个结点,也就是说,从它的右子结点出发一直访问左指针,最后就可以找到这个最左结点。
    如果一个结点没有右子树,那么需要再进一步考虑,不难知道:如果这个结点是其父结点的左结点,那么根据中序遍历规则,它的下一个结点就是它的父结点。
    第三种情况略复杂一些,当一个结点既没有右子树,也不是其父结点的左结点时,我们可以沿着指向父结点的指针一直向上遍历,直到找到一个是它自身的父结点的左孩子的结点,如果这样的结点存在,那么这个结点的父结点就是我们要找的下一个结点。
    举例:
    在这里插入图片描述
    以上图中的树为例,其中序遍历序列是:d,b,h,e,i,a,f,c,g。

    对应第一种情况(有右子树的情形),例如,图中结点b的下一个结点是h,结点a的下一个结点是f。

    对应第二种情况(没有右子树,但是其父结点的左结点的情形),例如,图中结点d的下一个结点是b,f的下一个结点是c。

    对应第三种情况(没有右子树,但是是其父结点的右结点),例如,为了找到结点g的下一个结点,我们沿着指向父结点的指针向上遍历,先到达结点c。由于结点c是父结点a的右结点,我们继续向上遍历到达结点a。由于结点a是树的根结点。它没有父结点。因此结点g没有下一个结点。

    编程实现(Java):

    /*
    public class TreeLinkNode {
        int val;
        TreeLinkNode left = null;
        TreeLinkNode right = null;
        TreeLinkNode next = null;
    
        TreeLinkNode(int val) {
            this.val = val;
        }
    }
    */
    public class Solution {
        public TreeLinkNode GetNext(TreeLinkNode pNode) {
            if (pNode.right != null) {
                TreeLinkNode node = pNode.right;
                while (node.left != null) {
                    node = node.left;
                }
                return node;
            } else {
                while (pNode.next != null) {
                    TreeLinkNode parent = pNode.next;
                    if (parent.left == pNode) {
                        return parent;
                    }
                    pNode = pNode.next;
                }
            }
    
            return null;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    11、《剑指offer》JZ28对称的二叉树

    题目描述:

    请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。

    解题思路:

    本题判断一棵树是不是对称的,和第18题可以对比分析:二叉树的镜像,和LeetCode第101题:101. 对称二叉树是同一道题。

    解法一:递归法

    《剑指Offer》中给出的解答是定义一种先遍历右子结点,再遍历左子结点的新遍历方法,称为前序遍历的对称遍历,实际上,我们不用这样,可以直接找到对称二叉树的规律:

    对称二叉树满足:根结点的左右子树相同,左子树的左子树和右子树的右子树相同,左子树的右子树和右子树的左子树相同即可。

    根据以上规律,直接使用递归便可以写出对应的代码,并不难理解,可以结合以下几个实例进行分析。

    解法二:迭代法(广度优先遍历)

    广度优先遍历的一般做法是借助队列,这里我们可以在初始化时把根节点入队两次。每次提取两个结点并比较它们的值(队列中每两个连续的结点应该是相等的,而且它们的子树互为镜像),然后将两个结点的左右子结点按相反的顺序插入队列中。当队列为空时,或者我们检测到树不对称(即从队列中取出两个不相等的连续结点)时,该算法结束。

    这一方法的关键是队列中出队列的两个连续的结点就应当是对称树中相等的结点。

    举例:
    在这里插入图片描述
    编程实现(Java):(解法一)

    /*
    public class TreeNode {
        int val = 0;
        TreeNode left = null;
        TreeNode right = null;
    
        public TreeNode(int val) {
            this.val = val;
    
        }
    
    }
    */
    public class Solution {
        boolean isSymmetrical(TreeNode pRoot) {
            if (pRoot == null) {
                return true;
            }
    
            return f(pRoot.left, pRoot.right);
        }
    
        public boolean f(TreeNode l, TreeNode r) {
            if (l == null && r == null) {
                return true;
            }
    
            if (l == null || r == null) {
                return false;
            }
    
            if (l.val != r.val) {
                return false;
            }
    
            return f(l.left, r.right) && f(l.right, r.left);
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    12、《剑指offer》JZ77按之字形顺序打印二叉树

    题目描述:

    请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。

    解题思路:

    这道题仍然是二叉树的遍历,相当于层次遍历,可以和第22题:从上往下打印二叉树 和第60题:把二叉树打印成多行 这几个题对比起来进行分析。

    相对而言,本题按之字形顺序打印二叉树是比较复杂的,短时间内不太好分析得到结论,可以通过具体的实例来进行分析,从具体的例子得出普遍的结论。
      在这里插入图片描述
    实际上,层次遍历我们都是借助一定的数据容器来实现的,比如按行打印使用的是队列。在本题,我们使用的是栈,具体分析如下:我们可以设置两个辅助栈,在打印某一层的结点时,将下一层的子结点保存到相应的栈里;如果当前打印的是奇数层(第一层、第三层等),则先保存左子节点再保存右子结点到第一个栈中,如果当前打印的是偶数层(第二层、第四层等),则先保存右子结点再保存左子结点到第二个栈中。
    举例:
    在这里插入图片描述
    编程实现(Java):

    import java.util.ArrayList;
    
    /*
    public class TreeNode {
        int val = 0;
        TreeNode left = null;
        TreeNode right = null;
    
        public TreeNode(int val) {
            this.val = val;
    
        }
    
    }
    */
    import java.util.*;
    public class Solution {
        public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
            ArrayList<ArrayList<Integer>> res = new ArrayList<>();
            Queue<TreeNode> queue = new LinkedList<>();
            if(pRoot == null){
                return res;
            }
    
            queue.add(pRoot);
            boolean flag = false;
            while(!queue.isEmpty()){
                ArrayList<Integer> list = new ArrayList<>();
                int size = queue.size();
                for(int i = 0; i < size; i++){
                    TreeNode node = queue.poll();
                    if(node.left != null){
                        queue.add(node.left);
                    }
                    if(node.right != null){
                        queue.add(node.right);
                    }
                    if(!flag){
                        list.add(node.val);
                    }else{
                        list.add(0, node.val);
                    }
                }
                res.add(list);
                flag = !flag;
            }
            return res;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    13、《剑指offer》JZ78把二叉树打印成多行

    题目描述:

    从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。

    解题思路:

    本题可类比第22题:从上往下打印二叉树,这两道题实际上是一回事,只不过这里我们多了一个分行打印的要求,实际上大同小异,稍加修改即可。

    在二叉树层次遍历上,我们使用的是队列,借助队列先进先出的性质实现,具体规律:每次打印一个结点时,如果该结点有子结点,则将子结点放到队列的末尾,接下来取出队列的头重复前面的打印动作,直到队列中所有的结点都打印完毕。在此基础上我们考虑这里的分行要求,不难想到我们只要增加两个变量即可:一个用于保存当前层中还没有打印的结点个数,另一个用于记录下一层结点的数目。而使用队列的话,实际上这两个变量可以统一用队列的长度来实现。

    举例:
    在这里插入图片描述
    编程实现(Java):

    import java.util.ArrayList;
    
    
    /*
    public class TreeNode {
        int val = 0;
        TreeNode left = null;
        TreeNode right = null;
    
        public TreeNode(int val) {
            this.val = val;
    
        }
    
    }
    */
    import java.util.*;
    public class Solution {
        ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
            ArrayList<ArrayList<Integer>> res = new ArrayList<>();
            Queue<TreeNode> queue = new LinkedList<>();
            if (pRoot == null) {
                return res;
            }
    
            queue.add(pRoot);
            while (!queue.isEmpty()) {
                int size = queue.size();
                ArrayList<Integer> list = new ArrayList<>();
                for (int i = 0; i < size; i++) {
                    TreeNode node = queue.poll();
                    if (node.left != null) {
                        queue.add(node.left);
                    }
    
                    if (node.right != null) {
                        queue.add(node.right);
                    }
    
                    list.add(node.val);
                }
                res.add(list);
            }
            return res;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    14、《剑指offer》JZ37序列化二叉树

    在这里插入图片描述
    在这里插入图片描述

    /*
    public class TreeNode {
        int val = 0;
        TreeNode left = null;
        TreeNode right = null;
    
        public TreeNode(int val) {
            this.val = val;
    
        }
    
    }
    */
    import java.util.*;
    public class Solution {
        String Serialize(TreeNode root) {
            if (root == null) {
                return "";
            }
    
            StringBuilder str = new StringBuilder();
            Queue<TreeNode> queue = new LinkedList<>();
            queue.add(root);
            while (!queue.isEmpty()) {
                TreeNode node = queue.poll();
                if (node != null) {
                    queue.add(node.left);
                    queue.add(node.right);
                    str.append(node.val + ",");
                } else {
                    str.append("#,");
                }
            }
            return str.toString();
        }
        TreeNode Deserialize(String str) {
            if (str == null || str.length() == 0) {
                return null;
            }
    
            String[] s = str.split(",");
            Queue<TreeNode> queue = new LinkedList<>();
            TreeNode root = new TreeNode(Integer.parseInt(s[0]));
            queue.add(root);
            int i = 1;
            while (!queue.isEmpty()) {
                TreeNode t = queue.poll();
                if (!s[i].equals("#")) {
                    TreeNode left = new TreeNode(Integer.parseInt(s[i]));
                    t.left = left;
                    queue.add(left);
                }
                i++;
                if (!s[i].equals("#")) {
                    TreeNode right = new TreeNode(Integer.parseInt(s[i]));
                    t.right = right;
                    queue.add(right);
                }
                i++;
            }
            return root;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    15、《剑指offer》JZ54二叉搜索树的第k个节点

    题目描述:

    给定一棵二叉搜索树,请找出其中的第k小的结点。例如(5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。

    解题思路:

    本题实际上比较简单,主要还是考察对树的遍历的理解,只要熟练掌握了树的三种遍历方式及其特点,解决本题并不复杂,很明显本题是对中序遍历的应用。

    对于本题,我们首先可以知道二叉搜索树的特点:左结点的值<根结点的值<右结点的值。因此,我们不难得到如下结论:如果按照中序遍历的顺序对一棵二叉搜索树进行遍历,那么得到的遍历序列就是递增排序的。因此,只要用中序遍历的顺序遍历一棵二叉搜索树,就很容易找出它的第k大结点。

    举例:
    在这里插入图片描述
    上面的树中序遍历的结果:2 3 4 5 6 7 8
    编程实现(Java):

    import java.util.*;
    
    /*
     * public class TreeNode {
     *   int val = 0;
     *   TreeNode left = null;
     *   TreeNode right = null;
     *   public TreeNode(int val) {
     *     this.val = val;
     *   }
     * }
     */
    
    public class Solution {
        /**
         * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
         *
         *
         * @param proot TreeNode类
         * @param k int整型
         * @return int整型
         */
        ArrayList<Integer> list = new ArrayList<>();
        public int KthNode (TreeNode proot, int k) {
            // write code here
            if (proot == null) {
                return -1;
            }
    
            order(proot);
            if (k > list.size() || k == 0) {
                return -1;
            }
            return list.get(k - 1);
        }
    
        public void order(TreeNode root) {
            if (root == null) {
                return;
            }
    
            order(root.left);
            list.add(root.val);
            order(root.right);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    16、《剑指offer》JZ86在二叉树中找到两个节点的最近公共祖先

    import java.util.*;
    
    /*
     * public class TreeNode {
     *   int val = 0;
     *   TreeNode left = null;
     *   TreeNode right = null;
     * }
     */
    
    public class Solution {
        /**
         * 
         * @param root TreeNode类 
         * @param o1 int整型 
         * @param o2 int整型 
         * @return int整型
         */
        public int lowestCommonAncestor (TreeNode root, int o1, int o2) {
            // write code here
            return f(root, o1, o2).val;
        }
    
        public TreeNode f(TreeNode root, int o1, int o2){
            if(root == null){
                return null;
            }
    
            if(root.val == o1 || root.val == o2){
                return root;
            }
    
            TreeNode left = f(root.left, o1, o2);
            TreeNode right = f(root.right, o1, o2);
            if(left == null){
                return right;
            }
    
            if(right == null){
                return left;
            }
    
            return root;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    17、《剑指offer》JZ7重建二叉树

    import java.util.*;
    /**
     * Definition for binary tree
     * public class TreeNode {
     *     int val;
     *     TreeNode left;
     *     TreeNode right;
     *     TreeNode(int x) { val = x; }
     * }
     */
    public class Solution {
        HashMap<Integer,Integer> map = new HashMap<>();
        public TreeNode reConstructBinaryTree(int [] pre,int [] vin) {
            if(pre == null){
                return null;
            }
    
            for(int i = 0; i < vin.length; i++){
                map.put(vin[i], i);
            }
    
            return f(pre, 0, pre.length - 1, vin, 0, vin.length - 1);
        }
    
        public TreeNode f(int[] pre, int l1, int r1, int[] vin, int l2, int r2){
            if(l1 > r1){
                return null;
            }
    
            TreeNode root = new TreeNode(pre[l1]);
            int i = map.get(pre[l1]);
    
            root.left = f(pre, l1 + 1, l1 + i - l2, vin, l2, i - 1);
            root.right = f(pre, l1 + i - l2 + 1, r1, vin, i + 1, r2);
            return root;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    18、《剑指offer》JZ84二叉树中和为某一值的路径(三)

    import java.util.*;
    
    /*
     * public class TreeNode {
     *   int val = 0;
     *   TreeNode left = null;
     *   TreeNode right = null;
     *   public TreeNode(int val) {
     *     this.val = val;
     *   }
     * }
     */
    
    public class Solution {
        /**
         * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
         *
         * 
         * @param root TreeNode类 
         * @param sum int整型 
         * @return int整型
         */
        int count = 0;
        public int FindPath (TreeNode root, int sum) {
            // write code here
            if(root == null){
                return 0;
            }
    
            dfs(root, sum);
            FindPath(root.left, sum);
            FindPath(root.right, sum);
            return count;
        }
    
        public void dfs(TreeNode root, int target){
            if(root == null){
                return;
            }
    
            target -= root.val;
            if(target == 0){
                count++;
            }
    
            dfs(root.left, target);
            dfs(root.right, target);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    19、《剑指offer》JZ82二叉树中和为某一值的路径(一)

    import java.util.*;
    
    /*
     * public class TreeNode {
     *   int val = 0;
     *   TreeNode left = null;
     *   TreeNode right = null;
     * }
     */
    
    public class Solution {
        /**
         * 
         * @param root TreeNode类 
         * @param sum int整型 
         * @return bool布尔型
         */
        public boolean hasPathSum (TreeNode root, int sum) {
            // write code here
            if(root == null){
                return false;
            }
    
            sum -= root.val;
            if(root.left == null && root.right == null && sum == 0){
                return true;
            }
    
            return hasPathSum(root.left, sum) || hasPathSum(root.right, sum);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
  • 相关阅读:
    【Linux修炼】5.vim详解
    离线安装harbor容器镜像仓库(harbor-v2.3.5)
    探索冒泡排序:C语言实践教程
    阿里云数据库大全_3分钟看懂阿里云RDS和NoSQL数据库汇总
    python经典百题之兔子出生问题
    react实现动态递增展示数字特效
    【算法】深入了解数据压缩算法(无损压缩和有损压缩)
    进阶JAVA篇-深入了解 Set 系列集合
    WSL下安装ubuntu 18.04 +meep进行FDTD仿真计算
    【Spring 源码】AOP 的加载原理(二)
  • 原文地址:https://blog.csdn.net/weixin_51405802/article/details/127575148