算法训练--二叉树(2)

480 阅读7分钟

二叉树的属性

101. 对称二叉树

  • 题目描述

    image.png

    image.png

  • 题解:对于二叉树是否对称,要比较的是根节点的左子树与右子树是不是相互翻转的,理解这一点就知道了其实我们要比较的是两个树(这两个树是根节点的左右子树)

    本题遍历只能是“后序遍历”,因为我们要通过递归函数的返回值来判断两个子树的内侧节点和外侧节点是否相等

    正是因为要遍历两棵树而且要比较内侧和外侧节点,所以准确的来说是一个树的遍历顺序是左右中,一个树的遍历顺序是右左中

    /**
    	递归
    */
    class Solution {
        public boolean isSymmetric(TreeNode root) {
            if(root==null) return true;
            return compare(root.left,root.right);
        }
        public boolean compare(TreeNode left, TreeNode right){
          	//首先排除空节点的情况
            if(left ==null && right!=null) return false;
            else if(left!=null && right==null) return false;
            else if(left==null && right==null) return true;
          	//排除了空节点,再排除数值不相同的情况
            else if(left.val!=right.val) return false;
            //此时才做递归,做下一层的判断
          	//左子树:左  右子树:右
            boolean outside=compare(left.left,right.right);
          	//左子树:右  右子树:左
            boolean inside=compare(left.right,right.left);
            return outside && inside;
        }
    }
    
    /**
    	迭代法
    */
    class Solution {
        public boolean isSymmetric(TreeNode root) {
            if(root==null) return true;
            Queue<TreeNode> queue=new LinkedList<>();
            queue.offer(root.left);
            queue.offer(root.right);
            while(!queue.isEmpty()){
                int size=queue.size();
                while(size>0){
                    size--;
                    TreeNode leftNode=queue.poll();
                    TreeNode rightNode=queue.poll();
                  	//左节点为空、右节点为空,此时说明是对称的
                    if(leftNode==null && rightNode==null) continue;
                    else if(leftNode==null || rightNode==null) return false;
                    else if(leftNode.val!=rightNode.val) return false;
                  	//注意左右对称
                    queue.offer(leftNode.left);
                    queue.offer(rightNode.right);
                    queue.offer(leftNode.right);
                    queue.offer(rightNode.left);
                }
            }
            return true;
        }
    }
    

104. 二叉树的最大深度

  • 题目描述

    image.png

  • 题解:根节点的高度就是二叉树的最大深度

    /**
    	递归
    */
    class Solution {
        public int maxDepth(TreeNode root) {
            if(root==null) return 0;
            return Math.max(maxDepth(root.left),maxDepth(root.right))+1;
        }
    }
    
    class solution {
        /**
         * 迭代法,使用层序遍历
         */
        public int maxdepth(TreeNode root) {
            if(root == null) {
                return 0;
            }
            Queue<TreeNode> queue = new linkedlist<>();
            queue.offer(root);
            int depth = 0;
            while (!queue.isempty()) {
                int size = queue.size();
                depth++;
                for (int i = 0; i < size; i++) {
                    TreeNode poll = queue.poll();
                    if (poll.left != null) {
                        queue.offer(poll.left);
                    }
                    if (poll.right != null) {
                        queue.offer(poll.right);
                    }
                }
            }
            return depth;
        }
    }
    

100. 相同的树

  • 题目描述

    image.png

  • 题解

    class Solution {
        public boolean isSameTree(TreeNode p, TreeNode q) {
            if(p==null && q!=null) return false;
            else if(p!=null && q==null) return false;
            else if(p==null && q==null) return true;
            else if(p.val!=q.val) return false;
          	//左对左
            boolean outside=isSameTree(p.left,q.left);
          	//右对右
            boolean inside=isSameTree(p.right,q.right);
            return outside && inside;
        }
    }
    
  • 题解2

    /**
    	迭代法
    */
    class Solution {
        public boolean isSameTree(TreeNode p, TreeNode q) {
            if(p==null && q==null) return true;
            if(p==null || q==null) return false;
            Queue<TreeNode> queue=new LinkedList<>();
            queue.offer(p);
            queue.offer(q);
            while(!queue.isEmpty()){
                int size=queue.size();
                while(size>0){
                    size--;
                    TreeNode leftNode=queue.poll();
                    TreeNode rightNode=queue.poll();
                    if(leftNode==null && rightNode==null) continue;
                    if(leftNode==null || rightNode==null || (leftNode.val!=rightNode.val)){
                        return false;
                    }
                  	//两个树都要保持一样的遍历顺序
                    queue.offer(leftNode.left);
                    queue.offer(rightNode.left);
                    queue.offer(leftNode.right);
                    queue.offer(rightNode.right);
                }
            }
            return true;
        }
    }
    

572. 另一棵树的子树

  • 题目描述

    ![image-20220423164950244](/Users/bin.wang/Library/Application Support/typora-user-images/image-20220423164950244.png)

  • 题解

    • 要么这两个树相等
    • 要么这个树是左树的子树
    • 要么这个树是右树的子树
    class Solution {
        public boolean isSubtree(TreeNode root, TreeNode subRoot) {
            if (root == null) {
                return false;
            }
            return (isSameTree(root,subRoot) || isSubtree(root.left,subRoot) || isSubtree(root.right,subRoot));
        }
    
        public boolean isSameTree(TreeNode tree1,TreeNode tree2){
            if(tree1==null && tree2==null) return true;
            else if(tree1==null || tree2==null) return false;
            else if(tree1.val!=tree2.val) return false;
            boolean outside=isSameTree(tree1.left,tree2.left);
            boolean inside=isSameTree(tree1.right,tree2.right);
            return outside && inside;
        }
    }
    

111. 二叉树的最小深度

  • 题目描述

    image.png

  • 题解

    最小深度是从根节点到最近叶子节点的最短路径上的节点数量。注意是叶子节点。什么是叶子节点,左右孩子都为空的节点才是叶子节点!

    111.二叉树的最小深度

    需要注意的是,只有当左右孩子都为空的时候,才说明遍历的最低点了。如果其中一个孩子为空则不是最低点

    class Solution {
        public int minDepth(TreeNode root) {
            if(root==null) return 0;
            //左
            int left=minDepth(root.left);
            //右
            int right=minDepth(root.right);
            //根
            //左右子节点存在一个为空,则不是最低点
            if(root.left==null && root.right!=null){
                return right+1;
            }
            if(root.right==null && root.left!=null){
                return left+1;
            }
            return Math.min(left,right)+1;
        }
    }
    
    /**
    	层序遍历迭代法
    */
    class Solution {
        public int minDepth(TreeNode root) {
            if(root==null) return 0;
            Queue<TreeNode> queue=new LinkedList<>();
            queue.offer(root);
            int depth=0;
            while(!queue.isEmpty()){
                int size=queue.size();
                depth++;
                while(size>0){
                    size--;
                    TreeNode node=queue.poll();
                  	//是叶子结点,直接返回depth,因为从上往下遍历,所以该值就是最小值
                    if(node.left==null && node.right==null){
                        return depth;
                    }
                    if(node.left!=null) queue.offer(node.left);
                    if(node.right!=null) queue.offer(node.right);
                }
            }
            return 0;
        }
    }
    

222. 完全二叉树的节点个数

  • 题目描述

    image.png

  • 题解

    /**
    	层序遍历
    */
    class Solution {
        public int countNodes(TreeNode root) {
            int res=0;
            if(root==null) return res;
            Queue<TreeNode> queue=new LinkedList<>();
            queue.offer(root);
            while(!queue.isEmpty()){
                int size=queue.size();
                while(size>0){
                    res++;
                    TreeNode node=queue.poll();
                    if(node.left!=null) queue.offer(node.left);
                    if(node.right!=null) queue.offer(node.right);
                    size--;
                }
            }
            return res;
        }
    }
    
    /**
    	通用递归
    */
    class Solution {
        public int countNodes(TreeNode root) {
            if(root==null) return 0;
            return countNodes(root.left)+countNodes(root.right)+1;
        }
    }
    

110. 平衡二叉树

  • 题目描述

    image.png

  • 题解

    • 二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数

    • 二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数

      求深度可以从上到下去查 所以需要前序遍历(中左右),而高度只能从下到上去查,所以只能后序遍历(左右中)

    110.平衡二叉树2

    既然要求比较高度,必然是要后序遍历

    /**
    	递归
    */
    class Solution {
        public boolean isBalanced(TreeNode root) {
            return getHeight(root)==-1?false:true;
        }
    		//返回以该节点为根节点的二叉树的高度,如果不是平衡二叉树则返回-1
        public int getHeight(TreeNode node){
            if(node==null) return 0;
          	//左
            int left=getHeight(node.left);
            if(left==-1) return -1;
          	//右
            int right=getHeight(node.right);
            if(right==-1) return -1;
          	//根
            if(Math.abs(left-right)>1){
                return -1;
            }else{
                return 1+Math.max(left,right);
            }
        }
    }
    

257. 二叉树的所有路径

  • 题目描述

    image.png

  • 题解

    这道题目要求从根节点到叶子的路径,所以需要前序遍历,这样才方便让父节点指向孩子节点,找到对应的路径

    在这道题目中将第一次涉及到回溯,因为我们要把路径记录下来,需要回溯来回退一一个路径在进入另一个路径,前序遍历以及回溯的过程如图:

    257.二叉树的所有路径

    /**
    	递归
    */
    class Solution {
        List<String> res;
        List<Integer> temp;
        public List<String> binaryTreePaths(TreeNode root) {
            res=new ArrayList<>();
            if(root==null) return res;
            temp=new ArrayList<>();
            traversal(root);
            return res;
        }
    
        public void traversal(TreeNode root){
            temp.add(root.val);
          	//叶子节点,递归终止条件
            if(root.left==null && root.right==null){
                StringBuilder sb=new StringBuilder();
                for(int i=0;i<temp.size()-1;i++){
                    sb.append(temp.get(i)).append("->");
                }
                sb.append(temp.get(temp.size()-1));
                res.add(sb.toString());
                return;
            }
          	//左
            if(root.left!=null){
                traversal(root.left);
              	//回溯
                temp.remove(temp.size()-1);
            }
          	//右
            if(root.right!=null){
                traversal(root.right);
              	//回溯
                temp.remove(temp.size()-1);
            }
        }
    }
    
  • 题解2:迭代法

    class Solution {
        public List<String> binaryTreePaths(TreeNode root) {
            List<String> res=new ArrayList<>();
            if(root==null) return res;
            Stack<Object> stack=new Stack<>();
          	//节点路径同时入栈
            stack.push(root);
            stack.push(root.val+"");
            while(!stack.isEmpty()){
                String path=(String)stack.pop();
                TreeNode node=(TreeNode)stack.pop();
              	//叶子节点
                if(node.left==null && node.right==null){
                    res.add(path);
                }
              	//左子节点不为空
                if(node.left!=null){
                    stack.push(node.left);
                    stack.push(path+"->"+node.left.val);
                }
              	//右子节点不为空
                if(node.right!=null){
                    stack.push(node.right);
                    stack.push(path+"->"+node.right.val);
                }
            }
            return res;
        }
    }
    

404. 左叶子之和

  • 题目描述

    image.png

  • 题解

    首先要注意是判断左叶子,不是二叉树左侧节点,所以不要上来想着层序遍历

    因为题目中其实没有说清楚左叶子究竟是什么节点,那么我来给出左叶子的明确定义:如果左节点不为空,且左节点没有左右孩子,那么这个节点的左节点就是左叶子

    /**
    	递归
    */
    class Solution {
        public int sumOfLeftLeaves(TreeNode root) {
            if(root==null) return 0;
            //左
            int left=sumOfLeftLeaves(root.left);
            //右
            int right=sumOfLeftLeaves(root.right);
            int rootVale=0;
            //根
            if(root.left!=null && root.left.left==null && root.left.right==null){
                rootVale=root.left.val;
            }
            int sum=left+right+rootVale;
            return sum;
        }
    }
    
    /**
    	层序遍历迭代法
    */
    class Solution {
        public int sumOfLeftLeaves(TreeNode root) {
            if(root==null) return 0;
            Queue<TreeNode> queue=new LinkedList<>();
            queue.offer(root);
            int res=0;
            while(!queue.isEmpty()){
                int size=queue.size();
                while(size>0){
                    size--;
                    TreeNode node=queue.poll();
                    if(node.left!=null){
                        queue.offer(node.left); 
                        if(node.left.left==null && node.left.right==null){
                            res+=node.left.val;
                        }
                    }
                    if(node.right!=null) queue.offer(node.right);
                }
            }
            return res;
        }
    }
    

513. 找树左下角的值

  • 题目描述

    image.png

    image.png

  • 题解

    /**
    	层序遍历迭代法
    	记录每一层的第一个值,即可得到最后一层的最左侧的值
    */
    class Solution {
        public int findBottomLeftValue(TreeNode root) {
            int res=0;
            Queue<TreeNode> queue=new LinkedList<>();
            queue.offer(root);
            while(!queue.isEmpty()){
                int size=queue.size();
                for(int i=0;i<size;i++){
                    TreeNode node=queue.poll();
                    if(i==0) res=node.val;
                    if(node.left!=null) queue.offer(node.left);
                    if(node.right!=null) queue.offer(node.right);
                }
            }
            return res;
        }
    }
    

112. 路径总和

  • 题目描述

    image.png

  • 题解

    class Solution {
        public boolean hasPathSum(TreeNode root, int targetSum) {
            if(root==null) return false;
            targetSum-=root.val;
          	//叶子节点
            if(root.left==null && root.right==null){
                return targetSum==0;
            }
            if(root.left!=null){
                boolean left=hasPathSum(root.left,targetSum);
              	//已经找到
                if(left){
                    return true;
                }
            }
            if(root.right!=null){
                boolean right=hasPathSum(root.right,targetSum);
              	//已经找到
                if(right){
                    return true;
                }
            }
            return false;
        }
    }
    

113. 路径总和 II

  • 题目描述

    ![image-20220423162045058](/Users/bin.wang/Library/Application Support/typora-user-images/image-20220423162045058.png)

  • 题解

    class Solution {
        List<List<Integer>> res;
        List<Integer> temp;
        public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
            res=new ArrayList<>();
            if(root==null) return res;
            temp=new ArrayList<>();
            getPathSum(root,targetSum);
            return res;
        }
    
        public void getPathSum(TreeNode root,Integer targetSum){
            temp.add(root.val);
          	//叶子节点
            if(root.left==null && root.right==null){
                int sum=0;
                for(Integer val:temp){
                    sum+=val;
                }
              	//递归终止条件
                if(sum==targetSum){
                    res.add(new ArrayList<>(temp));
                    return;
                }
            }
            if(root.left!=null){
                getPathSum(root.left,targetSum);
              	//回溯
                temp.remove(temp.size()-1);
            }
            if(root.right!=null){
                getPathSum(root.right,targetSum);
              	//回溯
                temp.remove(temp.size()-1);
            }
        }
    }
    

二叉树的修改与改造

226. 翻转二叉树

  • 题目描述

    image.png

  • 注意只要把每一个节点的左右孩子翻转一下,就可以达到整体翻转的效果

    这道题目使用前序遍历和后序遍历都可以,唯独中序遍历不方便,因为中序遍历会把某些节点的左右孩子翻转了两次

  • 题解1:递归法

    class Solution {
        public TreeNode invertTree(TreeNode root) {
            if(root==null){
                return root;
            }
            TreeNode temp=root.left;
            root.left=root.right;
            root.right=temp;
            invertTree(root.left);
            invertTree(root.right);
            return root;
        }
    }
    
  • 题解2:层序遍历(BFS)

    class Solution {
        public TreeNode invertTree(TreeNode root) {
            if(root==null) return root;
            Queue<TreeNode> queue=new LinkedList<>();
            queue.offer(root);
            while(!queue.isEmpty()){
                int size=queue.size();
                while(size>0){
                    size--;
                    TreeNode node=queue.poll();
                  	//交换左右子节点
                    TreeNode temp=node.left;
                    node.left=node.right;
                    node.right=temp;
                    if(node.left!=null) queue.offer(node.left);
                    if(node.right!=null) queue.offer(node.right);
                }
            }
            return root;
        }
    }
    

106. 从中序与后序遍历序列构造二叉树

  • 题目描述

    image.png

  • 题解

    思路:如何根据两个顺序构造一个唯一的二叉树,相信理论知识大家应该都清楚,就是以后序数组的最后一个元素为切割点,先切中序数组,根据中序数组,反过来在切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素106.从中序与后序遍历序列构造二叉树

    来看一下一共分几步:

    • 第一步:如果数组大小为零的话,说明是空节点了
    • 第二步:如果不为空,那么取后序数组最后一个元素作为节点元素
    • 第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点
    • 第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)
    • 第五步:切割后序数组,切成后序左数组和后序右数组
    • 第六步:递归处理左区间和右区间
    /**
     * Definition for a binary tree node.
     * public class TreeNode {
     *     int val;
     *     TreeNode left;
     *     TreeNode right;
     *     TreeNode() {}
     *     TreeNode(int val) { this.val = val; }
     *     TreeNode(int val, TreeNode left, TreeNode right) {
     *         this.val = val;
     *         this.left = left;
     *         this.right = right;
     *     }
     * }
     */
    class Solution {
        public TreeNode buildTree(int[] inorder, int[] postorder) {
            return traversal(inorder,0,inorder.length,postorder,0,postorder.length);
        }
        public TreeNode traversal(int[] inorder,Integer inStart,Integer inEnd,int[] postorder,Integer postStart,Integer postEnd){
            if(inEnd-inStart<1) return null;
            //只有一个元素
            if(inEnd-inStart==1) return new TreeNode(inorder[inStart]);
            //根据后序最后元素得到根
            int rootValue=postorder[postEnd-1];
            TreeNode result=new TreeNode(rootValue);
            //根据root值找出在中序中的位置
            int rootIndex=0;
            for(int i=inStart;i<inEnd;i++){
                if(inorder[i]==rootValue){
                    rootIndex=i;
                    break;
                }
            }
            //根据rootIndex划分左右子树
            result.left=traversal(inorder,inStart,rootIndex,postorder,postStart,postStart+(rootIndex-inStart));
            //注意边界 中序需要跳过中间的root  后序需要排除末尾的root
            result.right=traversal(inorder,rootIndex+1,inEnd,postorder,postStart+(rootIndex-inStart),postEnd-1);
            return result;
        }
    }
    

105. 从前序与中序遍历序列构造二叉树

  • 题目描述

    ![image-20220423220209866](/Users/bin.wang/Library/Application Support/typora-user-images/image-20220423220209866.png)

  • 题解

    class Solution {
        public TreeNode buildTree(int[] preorder, int[] inorder) {
            return traversal(preorder,0,preorder.length,inorder,0,inorder.length);
        }
        public TreeNode traversal(int[] preorder,Integer preStart,Integer preEnd,int[] inorder,Integer inStart,Integer inEnd){
            if(inEnd-inStart<1) return null;
            if(inEnd-inStart==1) return new TreeNode(inorder[inStart]);
            //根据前序第一个节点就是root
            int rootVal=preorder[preStart];
            TreeNode result=new TreeNode(rootVal);
            //根据值找到中序的根节点所在位置
            int rootIndex=0;
            for(int i=inStart;i<inEnd;i++){
                if(inorder[i]==rootVal){
                    rootIndex=i;
                    break;
                }
            }
            //根据rootIndex划分左右子树
            //注意边界 前序要排除头部的root 中序要跳过中间的root
            result.left=traversal(preorder,preStart+1,preStart+(rootIndex-inStart)+1,inorder,inStart,rootIndex);
            result.right=traversal(preorder,preStart+(rootIndex-inStart)+1,preEnd,inorder,rootIndex+1,inEnd);
            return result;
        }
    }
    

654. 最大二叉树

  • 题目描述

    image.png

    ![image-20220423222200771](/Users/bin.wang/Library/Application Support/typora-user-images/image-20220423222200771.png)

  • 题解

    654.最大二叉树

    构造树一般采用的是前序遍历,因为先构造中间节点,然后递归构造左子树和右子树

    class Solution {
        public TreeNode constructMaximumBinaryTree(int[] nums) {
            return traversal(nums,0,nums.length);
        }
      	//左闭右开区间[left, right),构造二叉树
        public TreeNode traversal(int[] nums,Integer l,Integer r){
            if(l>=r) return null;
          	//分割点下标:maxValueIndex
            int maxIndex=l;
            for(int i=l;i<r;i++){
                if(nums[i]>nums[maxIndex]){
                    maxIndex=i;
                }
            }
            TreeNode result=new TreeNode(nums[maxIndex]);
          	//左闭右开:[left, maxValueIndex)
            result.left=traversal(nums,l,maxIndex);
          	//左闭右开:[maxValueIndex + 1, right)
            result.right=traversal(nums,maxIndex+1,r);
            return result;
        }
    }
    

617. 合并二叉树

  • 题目描述

    image.png

  • 题解

    如何同时遍历两个二叉树呢?

    其实和遍历一个树逻辑是一样的,只不过传入两个树的节点,同时操作,以前序遍历为例

    617.合并二叉树

    /**
    	前序
    */
    class Solution {
        public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
            if(root1==null) return root2;
            if(root2==null) return root1;
            //根
            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;
            //左
            root1.left=mergeTrees(root1.left,root2.left);
            //根
            root1.val+=root2.val;
            //右
            root1.right=mergeTrees(root1.right,root2.right);
            return root1;
        }
    }