LeetCode----树

104.二叉树的最大深度

题目大意

给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
在这里插入图片描述

解题思路

递归来实现。分别递归的计算左子树和右子树的高度,找出他们中的一个最大值,再加上1也就是根节点,就是整棵树的最大深度了。

代码实现

class Solution {
    public int maxDepth(TreeNode root) {
        if(root==null){
            return 0;
        }
        return Math.max(maxDepth(root.left)+1,maxDepth(root.right)+1);
    }
}

110.平衡树

题目大意

给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。
在这里插入图片描述

解题思路

利用递归来解决:
如果左右子树的高度差大于1就不满足平衡二叉树的条件,直接返回false。在满足高度差大于1的情况下,左右子树分别也是平衡二叉树,那么整棵树才是一颗平衡二叉树。

代码实现

class Solution {
    public boolean isBalanced(TreeNode root) {
        if(root==null){
            return true;
        }
        if(Math.abs(height(root.left)-height(root.right))>1){
            return false;
        }else if(isBalanced(root.left)&&isBalanced(root.right)){
            return true;
        }   
        return false;
    }
    public int height(TreeNode root){
        if(root==null){
            return 0;
        }
        return Math.max(height(root.left),height(root.right))+1;
    }
        
}

543.二叉树的直径

题目大意

给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。
在这里插入图片描述

解题思路

一条路径的长度等于该路径上的节点数减去一,所以求路径等效于求节点数的最大值减一。任意一条路径都可以看作以某个节点为起点,从其左儿子和右儿子向下遍历的路径拼接得到。
在这里插入图片描述
假如对于某节点的左儿子向下遍历经过的最多节点数为L(2),右儿子向下遍历得到最多的节点数为R(3),那么以该节点为起点的路径经过的节点数最大可以为L+R+1(2+3+1)。则以该节点为起点的最大路径长度为L+R+1-1。
我们记节点node为起点的路径经过节点数的最大值为dnode,那么二叉树的直径就是所有节点dnode的最大值减去1。
算法流程:定义一个递归函数depth(node)计算dnode,函数返回该节点为根的子树的深度。先递归调用左儿子和右儿子求得他们为根的子树的深度L和R,则该节点为根的子树的深度即为max(L,R)+1。该节点的dnode值为L+R+1。
递归搜索每个节点并设一个全局变量ans记录dnode的最大值,最后返回ans-1,即为树的直径。

实现代码

class Solution {
    private int ans=0;
    public int diameterOfBinaryTree(TreeNode root) {
        depth(root);
        return ans; 
    }
    //里面已经实现了递归调用
    public int depth(TreeNode root){
        if(root==null){
            return 0;
        }
        //左子树的高度
        int leftDepth=depth(root.left);
        //右子树的高度
        int rightDepth=depth(root.right);
        ans=Math.max(ans,leftDepth+rightDepth);
        return Math.max(leftDepth,rightDepth)+1;
    }  
}

226.翻转二叉树

题目大意

翻转一棵二叉树。
在这里插入图片描述

解题思路

利用递归,首先将左子树保存,然后将右子树翻转赋值给左子树,再将保存的左子树翻转赋值给右子树。

代码实现

class Solution {
    public TreeNode invertTree(TreeNode root) {
        if(root==null){
            return null;
        }
        TreeNode left=root.left;
        root.left=invertTree(root.right);
        root.right=invertTree(left);
        return root;

    }
}

617.合并二叉树

题目描述

给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。
你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。
在这里插入图片描述

解题思路

(一)递归实现
递归的终止条件是root1的节点为null或者是root2的节点为null。
时间复杂度O(N)
空间复杂度O(h),h为树的高度。
(二)迭代实现
迭代实现用的是广度优先算法,需要借助额外的数据结构来实现,可以借助栈或者队列来实现。
只要两棵树的左节点都不为null,就将他放入到队列中,同理,只要两棵树的右节点都不为null,也放入到队列中。然后不断地将他们在队列中去除,相加。
如果出现树1的left节点为null树2的left节点不为null,就直接将树2的left直接赋值给树1的left。同理,如果树1的right为null而树2的right不为null,就直接将树2的right赋值给树1的right。
时间复杂度:O(N)
空间复杂度:O(N),对于满二叉树,要保存所有的叶子节点,需要N/2个节点。

代码实现

(一)递归实现

class Solution {
    public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
        //dfs
        if(root1==null&&root2==null){
            return null;
        }
        return dfs(root1,root2);
    }
    public TreeNode dfs(TreeNode root1,TreeNode root2){
        if(root1==null||root2==null){
            return root1==null?root2:root1;
        }
        root1.val+=root2.val;
        root1.left=dfs(root1.left,root2.left);
        root1.right=dfs(root1.right,root2.right);
        return root1;
    }
}

(二)迭代实现(广度优先算法)

class Solution {
    public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
        //迭代实现
        if(root1==null||root2==null){
            return root1==null?root2:root1;
        }
        LinkedList<TreeNode> queue=new LinkedList<>();
        queue.add(root1);
        queue.add(root2);
        while(queue.size()>0){
            TreeNode r1=queue.remove();
            TreeNode r2=queue.remove();
            r1.val+=r2.val;
            if(r1.left!=null&&r2.left!=null){
                queue.add(r1.left);
                queue.add(r2.left);
            }else if(r1.left==null){
                r1.left=r2.left;
            }
            if(r1.right!=null&&r2.right!=null){
                queue.add(r1.right);
                queue.add(r2.right);
            }else if(r1.right==null){
                r1.right=r2.right;
            }
            
        }
        return root1;
        
    }
}

112. 路经总和

题目大意

给你二叉树的根节点 root 和一个表示目标和的整数 targetSum ,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和 targetSum 。
叶子节点 是指没有子节点的节点。
在这里插入图片描述

解题思路

注意终止条件的设定是targetSum等于当前根节点的值,而不是0。

代码实现

class Solution {
    public boolean hasPathSum(TreeNode root, int targetSum) {
        if(root==null){
            return false;
        }
        if(root.left==null&&root.right==null&&targetSum==root.val){
            return true;
        }
       return hasPathSum(root.left,targetSum-root.val)||hasPathSum(root.right,targetSum-root.val);

    }
}

437.路径总和III

题目大意

给定一个二叉树,它的每个结点都存放着一个整数值。
找出路径和等于给定数值的路径总数。
路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)
二叉树不超过1000个节点,且节点数值范围是 [-1000000,1000000] 的整数。
在这里插入图片描述

解题思路

(一)递归实现
(二)前缀和
前缀和就是到达当前元素的路径上,之前所有元素的和。
如果前缀总和为currSum,在节点A和节点B处相差target,则位于节点A和节点B之间的元素之和为target。
如果一条路径上存在环,那么前缀法就不能用了。
抵达当前节点(即节点B)后,将前缀和累加,然后查找在前缀和上有没有前缀和currSum-target的节点(即A节点),存在即表示从到B有一条路径之和满足条件的情况。
结果加上满足前缀和currSum-target的节点的数量。然后递归进入左右子树。
左右子树遍历完成之后,回到当前层,需要把当前节点添加的前缀和去除,避免回溯之后影响上一层,因为思想是前缀和,不属于前缀的,我们就要去掉他。
(1)前缀和定义
一个节点的前缀和就是该节点到根之间的路径和。
(2)前缀对于本题的作用
题目要求给出路径和等于给定数值的路径总数,而两节点间的路径和=两节点的前缀和之差。所以我们遍历整棵树一次,记录每个节点的前缀和,并查询该节点的祖先节点中符合条件的个数,将这个数量加到最终的结果上。
(3)HashMap中存的是什么
HashMap的key是前缀和,value是该前缀和的数量,记录数量是因为有出现复数路径的可能。
(4)恢复状态的意义
由于题目要求:路径方向必须是向下的,所以只能是从父节点到子节点。
当我们讨论两个节点的前缀差值时,有一个前提:一个节点必须是另一个节点的祖先节点换句话说,当我们把一个节点的前缀和信息更新到map里时,它应当只对子节点们有效。状态恢复代码的作用就是:在遍历完一个节点的所有子节点后,将其从map中除去。
getOrDefault() 方法获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值。

代码实现

(一)递归实现

class Solution {
    public int pathSum(TreeNode root, int sum) {
        if(root==null){
            return 0;
        }
        int count=0;
        count=pathSumStartWithRoot(root,sum)+pathSum(root.left,sum)+pathSum(root.right,sum);
        return count;
    }
    public int pathSumStartWithRoot(TreeNode root,int sum){
        int ans=0;
        if(root==null){
            return 0;
        }
        if(sum==root.val){
            ans++;
        }
        ans+= pathSumStartWithRoot(root.left,sum-root.val)+pathSumStartWithRoot(root.right,sum-root.val);
        return ans;
    }
}

(二)前缀法

class Solution {
    //key表示前缀和,value表示前缀和出现的次数
    Map<Integer,Integer> prefixMap;
    int target;
    public int pathSum(TreeNode root, int sum) {
        prefixMap=new HashMap<>();
        target=sum;
        prefixMap.put(0,1);
        //找以root为根节点,和为0的路径数
        return recur(root,0);
    }
    private int recur(TreeNode node,int curSum){
        if(node==null){
            return 0;
        }
        //res用来记录路径数
        int res=0;
        //curSum用来记录当前的前缀和
        curSum+=node.val;
        //获取指定key对应的value
        //获取前缀和为curSum-target的节点数,当前节点的前缀和为curSum
        res+=prefixMap.getOrDefault(curSum-target,0);
        //将前缀和为curSum的节点数加一
        prefixMap.put(curSum,prefixMap.getOrDefault(curSum,0)+1);
        //求左孩子的前缀和为curSum的节点数目
        int left=recur(node.left,curSum);
        //求右孩子的前缀和为curSum的节点数目
        int right=recur(node.right,curSum);
        //res记录前缀和为curSum的总的节点的数目
        res=res+left+right;
        //状态恢复
        prefixMap.put(curSum,prefixMap.get(curSum)-1);
        return res;
    }
    
}

572.另一个树的子树

题目大意

给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树。s 的一个子树包括 s 的一个节点和这个节点的所有子孙。s 也可以看做它自身的一棵子树。
在这里插入图片描述

解题思路

递归来实现,如果根节点相同,就继续判断下面的节点,如果不同就判断是不是左子树或者右子树的子树。

代码实现

class Solution {
    public boolean isSubtree(TreeNode s, TreeNode t) {
        if(s==null){
            return false;
        }    
        return isSame(s,t)||isSubtree(s.left,t)||isSubtree(s.right,t); 
    }
    public boolean isSame(TreeNode s,TreeNode t){
        if(s==null&&t==null){
            return true;
        }else if(s==null||t==null){
            return false;
        }
        if(s.val!=t.val){
            return false;
        }
        return isSame(s.left,t.left)&&isSame(s.right,t.right);
    }
}

101.对称二叉树

题目大意

给定一个二叉树,检查它是否是镜像对称的。
在这里插入图片描述

解题思路

递归来解决:递归的来判断左右子树的值是否相同。

代码实现

class Solution {
    public boolean isSymmetric(TreeNode root) {
        if(root==null){
            return true;
        }
        return isSame(root.left,root.right);
    }
    public boolean isSame(TreeNode root1,TreeNode root2){
        if(root1==null&&root2==null){
            return true;
        }else if(root1==null||root2==null){
            return false;
        }
        if(root1.val!=root2.val){
            return false;
        }
        return isSame(root1.left,root2.right)&&isSame(root1.right,root2.left);
    }
}

111.二叉树的最小深度

题目大意

给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明:叶子节点是指没有子节点的节点。
在这里插入图片描述

解题思路

递归来解决:递归求左右子树的最小高度,有一点需要注意的是,当整棵树只存在左子树或者右子树的时候,需要特殊处理一下,不能只返回两棵子树中的最小值了,因为会导致结果为0。

if(root.left==null||root.right==null){
            return left+right+1;
        }

代码实现

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 left+right+1;
        }
        return Math.min(left,right)+1;        
    }
}

404.左叶子之和

题目大意

计算给定二叉树的所有左叶子之和。
在这里插入图片描述

解题思路

判断左子树是否是一个叶子节点,如果是的话就将左叶子节点的值和右子树的左叶子节点的值,如果不是叶子节点的话就递归的将左右子树左叶子节点的值相加。

代码实现

class Solution {
    public int sumOfLeftLeaves(TreeNode root) {
        if(root==null){
            return 0;
        }
        if(isLeaf(root.left)){
            return root.left.val+sumOfLeftLeaves(root.right);
        }
        return sumOfLeftLeaves(root.left)+sumOfLeftLeaves(root.right);
    }
    public boolean isLeaf(TreeNode root){
        if(root==null){
            return false;
        }
        if(root.left==null&&root.right==null){
            return true;
        }
        return false;
    }
}

687.最长同值路径

题目大意

给定一个二叉树,找到最长的路径,这个路径中的每个节点具有相同值。 这条路径可以经过也可以不经过根节点
在这里插入图片描述

解题思路

递归的找到左右子树的具有相同值的最长路径。

代码实现

class Solution {
    int ans=0;
    public int longestUnivaluePath(TreeNode root) {
        if(root==null){
            return 0;
        }
        arrowLength(root);
        return ans;
    }
    //返回从root节点延伸出得最长箭头得长度
    public int arrowLength(TreeNode root){
        if(root==null){
            return 0;
        }
        int left=arrowLength(root.left);
        int right=arrowLength(root.right);
        int leftLength=0;
        int rightLength=0;
        if(root.left!=null&&root.val==root.left.val){
            leftLength+=left+1;
        }
        if(root.right!=null&&root.val==root.right.val){
            rightLength+=right+1;
        }
        ans=Math.max(ans,leftLength+rightLength);
        return Math.max(leftLength,rightLength); 
    }
}

337.打家劫舍 III

题目大意

在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。
计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
在这里插入图片描述

解题思路

(一)暴力递归-最优子结构
(二)针对重复子问题进行优化
斐波那契数列使用记忆化进行优化,使用数组将每次得计算结果保存起来,下次如果再来计算就在缓存中取,不再计算了,这样保证了每个数字只计算一次。由于二叉树不适合拿数组来当缓存,所以使用哈希表来存储结果,TreeNode当作key,能偷的钱当作value。
(三)终极解法
上面两种解法用到了孙子节点,计算爷爷节点能偷的钱还要同时去计算孙子节点投的钱,虽然有了记忆化,但是还是有性能损耗。
定义一个长度为2的数组:int[] res=new int[2];0代表不偷,1代表偷,任何一个节点能偷到的最大钱的状态可以定义为:

  1. 当前节点选择不偷:当前节点能偷到的最大钱数=左孩子能偷到的最大钱数(不管偷不偷)+右孩子能偷到的最大钱数(不管偷不偷)。
  2. 当前节点选择偷:当前节点能偷到的最大钱数=左孩子选择不偷时能得到的最大钱数+右孩子选择不偷时能得到的最大钱数。

代码实现

(一)暴力递归-最优子结构

class Solution {
    public int rob(TreeNode root) {
        if(root==null){
            return 0;
        }
        int val1=root.val;
        if(root.left!=null){
            val1+=rob(root.left.left)+rob(root.left.right);
        }
        if(root.right!=null){
            val1+=rob(root.right.left)+rob(root.right.right);
        }
        int val2=rob(root.left)+rob(root.right);
        return Math.max(val1,val2);
    }
}

(二)针对重复子问题进行优化

class Solution {
    public int rob(TreeNode root) {
        //定义一个HashMap,其中key值存放节点,value存放盗取的最高金额
        HashMap<TreeNode,Integer> map=new HashMap<>();
        return robInternal(root,map);
    }
    public int robInternal(TreeNode root,HashMap<TreeNode,Integer> map){
        if(root==null){
            return 0;
        }
        if(map.containsKey(root)){
            return map.get(root);
        }
        int val1=root.val;
        if(root.left!=null){
            val1+=robInternal(root.left.left,map)+robInternal(root.left.right,map);
        }
        if(root.right!=null){
            val1+=robInternal(root.right.left,map)+robInternal(root.right.right,map);
        }
        int val2=robInternal(root.left,map)+robInternal(root.right,map);
        int res=Math.max(val1,val2);
        map.put(root,res);
        return res;
    }
}

(三)终极版

class Solution {
    public int rob(TreeNode root) {
        int[] res=robInternal(root);
        return Math.max(res[0],res[1]);
    }
    public int[] robInternal(TreeNode root){
        if(root==null){
            return new int[2];
        }
        int[] left=robInternal(root.left);
        int[] right=robInternal(root.right);
        int[] res=new int[2];
        //当前节点选择不偷
        res[0]=Math.max(left[0],left[1])+Math.max(right[0],right[1]);
        //当前节点选择偷
        res[1]=left[0]+right[0]+root.val;
        return res;
    }
}

671.二叉树中第二小的节点

题目大意

给定一个非空特殊的二叉树,每个节点都是正数,并且每个节点的子节点数量只能为 2 或 0。如果一个节点有两个子节点的话,那么该节点的值等于两个子节点中较小的一个。
更正式地说,root.val = min(root.left.val, root.right.val) 总成立。
给出这样的一个二叉树,你需要输出所有节点中的第二小的值。如果第二小的值不存在的话,输出 -1 。
在这里插入图片描述

解题思路

根节点永远保存整棵树的最小值
如果根节点的值左子节点的值相等,就去找左子树的第二最小值,如果不存在就直接返回右子节点的值。如果存在就去找左右子树中的较小的那个第二最小值。同理对于右子树也是同样的做法。

代码实现

class Solution {
    public int findSecondMinimumValue(TreeNode root) {
        //根节点永远保存整棵树的最小值
        if(root==null){
            return -1;
        }
        if(root.left==null&&root.right==null){
            return -1;
        }
        int leftVal=root.left.val;
        int rightVal=root.right.val;
        //求左子树的第二小值
        if(root.val==leftVal){
            leftVal=findSecondMinimumValue(root.left);
        }
        //求右子树的第二小值
        if(root.val==rightVal){
            rightVal=findSecondMinimumValue(root.right);
        }
        if(leftVal!=-1&&rightVal!=-1){
            return Math.min(leftVal,rightVal);
        }
        if(leftVal==-1){
            return rightVal;
        }
        return leftVal;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值