代码随想录一刷Day8 二叉树(二)递归法

总是搞不明白递归法应该怎么做 

222. 完全二叉树的节点个数 - 力扣(LeetCode)

class Solution {
    public int countNodes(TreeNode root) {
        // 迭代法
        // return count1(root);
        // 递归法
        return count2(root);
    }
    public int count1(TreeNode root)
    {
        int sum=0;
        Deque<TreeNode> que=new LinkedList<>();
        if(root==null) return 0;
        que.offer(root);
        while(!que.isEmpty())
        {
            int len=que.size();
            sum+=len;
            for(int i=0;i<len;i++)
            {
                TreeNode tmp=que.poll();
                if(tmp.left!=null)
                que.offer(tmp.left);
                if(tmp.right!=null)
                que.offer(tmp.right);
            }
        }
        return sum;
    }
    public int count2(TreeNode root)
    {
        if(root==null) return 0;
    
            int left=count2(root.left);
            int right=count2(root.right);
            int sum=left+right+1;//加一这个操作很重要 而且这一步骤要在最后做,表示遍历了此节点
        return sum;
    }
}

还有完全二叉树的做法:利用完全二叉树的性质,若此树为完全二叉树,则沿最左侧遍历和最右侧遍历的结点相同,采用后序遍历,子树节点计算完毕后返还给上一个节点。

class Solution {
    /**
     * 针对完全二叉树的解法
     *
     * 满二叉树的结点数为:2^depth - 1
     */
    public int countNodes(TreeNode root) {
        if (root == null) return 0;
        TreeNode left = root.left;
        TreeNode right = root.right;
        int leftDepth = 0, rightDepth = 0; // 这里初始为0是有目的的,为了下面求指数方便
        while (left != null) {  // 求左子树深度
            left = left.left;
            leftDepth++;
        }
        while (right != null) { // 求右子树深度
            right = right.right;
            rightDepth++;
        }
        if (leftDepth == rightDepth) {
            return (2 << leftDepth) - 1; // 注意(2<<1) 相当于2^2,所以leftDepth初始为0
        }
        return countNodes(root.left) + countNodes(root.right) + 1;
    }

 110. 平衡二叉树 - 力扣(LeetCode)

为什么求深度适合用前序遍历,而求高度适合用后序遍历?

  1. 求深度使用前序遍历

    • 在前序遍历中,我们首先访问根节点,然后递归地遍历左子树和右子树。
    • 求深度时,我们关注的是从根节点到达任一叶子节点的最短路径。在前序遍历的过程中,可以很容易地追踪到达当前节点的路径长度,并在到达叶子节点时更新深度(如果当前路径长度小于已知最小深度)。
    • 这种遍历方式能够在遍历的过程中及时更新深度信息,不需要等到整棵树遍历完毕。
  2. 求高度使用后序遍历

    • 在后序遍历中,我们先递归地遍历左子树和右子树,然后访问根节点。
    • 求高度时,我们关注的是从根节点到达任一叶子节点的最长路径。在后序遍历的过程中,当我们访问一个节点时,其左右子树已经被完全遍历,因此可以直接获取左右子树的高度。
    • 通过比较左右子树的高度,取最大值然后加上当前节点自身的高度(通常为1),我们可以逐步构建出从下到上的高度信息,直到达到根节点。

总结来说,前序遍历适合于在遍历过程中更新和比较路径长度,适合求深度;而后序遍历在访问每个节点时,其子树的信息已经完全可用,适合于从底部向上计算高度,我们之前求节点数的时候也用的是后序遍历。

class Solution {
    public boolean isBalanced(TreeNode root) {
        return getHeight(root)!=-1;
    }
    public int getHeight(TreeNode root)
    {
        if(root==null) return 0;
        //后序遍历
        // 左侧递归
        int leftHight=getHeight(root.left);
        if (leftHight==-1) return -1;
        // 右侧递归
        int rightHight=getHeight(root.right);
        if(rightHight==-1) return -1;
        //中间节点,进行操作
        if(Math.abs(leftHight-rightHight)>1) return -1;
        int hight=Math.max(rightHight,leftHight)+1;//左右子树的节点的最大值+1就是此节点深度
        return hight;//返还给上一层
    }
}

104. 二叉树的最大深度 - 力扣(LeetCode)

前序遍历求深度

class Solution {
    private int result=0;
    public int maxDepth(TreeNode root) {
        if(root==null)
        {
            return 0;
        }
       count(root,1);//root存在的时候depth已经是1了

       return result;
    }
    public void count(TreeNode root,int depth)
    {
        //在这个函数里面递归
        //中
         if(depth>result)
         result=depth;//更新result
        //  左
         if(root.left!=null)
         {
             count(root.left,depth+1);
         }
        //  右
         if(root.right!=null)
         {
             count(root.right,depth+1);
         }
         return;

    }
}

111. 二叉树的最小深度 - 力扣(LeetCode) 

这道题我们虽然求的也是深度,但是用的是后序遍历

class Solution {
    public int minDepth(TreeNode root) {
        if (root == null)
            return 0;
        return countDepth(root);
    }

    public int countDepth(TreeNode root) {
        if (root == null) {
            return Integer.MAX_VALUE; // 如果节点不存在,返回一个大数,这样不会影响最小深度的计算
        }
        if (root.left == null && root.right == null) {
            return 1; // 叶子节点,深度为1
        }
        int leftDepth = countDepth(root.left);
        int rightDepth = countDepth(root.right);

        return Math.min(leftDepth, rightDepth) + 1; // 返回左右子树的最小深度 + 当前节点的深度
    }
}
class Solution {
    /**
     * 递归法,相比求MaxDepth要复杂点
     * 因为最小深度是从根节点到最近**叶子节点**的最短路径上的节点数量
     */
    public int minDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        int leftDepth = minDepth(root.left);
        int rightDepth = minDepth(root.right);
        if (root.left == null) {
            return rightDepth + 1;
        }
        if (root.right == null) {
            return leftDepth + 1;
        }
        // 左右结点都不为null
        return Math.min(leftDepth, rightDepth) + 1;
    }
}

 

第一种方法

这种方法在递归函数 countDepth 中首先检查是否到达了叶子节点(即左右子树都为空)。然后,对于非叶子节点,它递归地计算左右子树的深度,并从中选择最小值。这个方法在递归中显式地处理每一层的深度计数,并在递归调用前后对深度进行增加和减少。关键点在于:

  • 它对每个节点的左右子树进行了递归,即使其中一个子树为空(此时返回 Integer.MAX_VALUE)。
  • 在每次递归之前和之后,它显式地修改 depth 参数的值。

第二种方法

这个方法采用了更简洁的递归逻辑。对于每个节点,它首先检查节点是否为空,然后递归地计算左右子树的深度。关键的不同之处在于:

  • 当一个节点的左或右子树为空时,它不会对那个空的子树进行递归。相反,它只关注那个存在的子树。这样做避免了对空子树进行不必要的递归调用。
  • 它不需要在递归调用中显式处理深度计数。递归调用自然地处理这个逻辑,因为每次递归调用返回的是子树的最小深度,再加上当前节点的深度(即 +1)。

总结

  • 第一种方法在递归过程中对深度进行了显式管理,且总是对左右子树进行递归,即使其中一个为空。
  • 第二种方法的递归逻辑更加直观和简洁。它只在子树存在时进行递归,并且在每次递归返回时自然地累加深度。

 

257. 二叉树的所有路径 - 力扣(LeetCode) 

class Solution {
    public List<String> binaryTreePaths(TreeNode root) {
        List<String> res=new ArrayList<>();
        if(root==null)
        {
            return res;
        }
        List<Integer> path=new ArrayList<>();
        travelsal(root,path,res);
        return res;
    }

    private void travelsal(TreeNode root,List<Integer> path,List<String> res)
    {
  
        //前序遍历
        path.add(root.val);
        //结束逻辑:当当前节点root的左右孩子都为空的时候,root就是叶子节点
        //此时可以把path加上->加入到res中 结束向下递归
        if(root.left==null&&root.right==null)
        {
            StringBuilder sb=new StringBuilder();
            for(int i=0;i<path.size()-1;i++)
            {
                sb.append(path.get(i)).append("->");
            }
            sb.append(path.get(path.size()-1));
            res.add(sb.toString());//添加一个路径
            return;
        }
        if(root.left!=null){
            travelsal(root.left,path,res);
            // path在上面的递归跳出后已经被改变,所以需要移除root.left
            path.remove(path.size()-1);
        }
        if(root.right!=null)
        {
            travelsal(root.right,path,res);
            path.remove(path.size());
        }
    }
}

怎么写一个递归?

  • 分解问题:考虑如何将问题分解成更小的部分,并使用相同的函数来解决这些更小的问题。
  • 自我调用:在递归案例中,函数应该以新的参数调用自身。

  • 23
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值