总是搞不明白递归法应该怎么做
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;
}
为什么求深度适合用前序遍历,而求高度适合用后序遍历?
-
求深度使用前序遍历:
- 在前序遍历中,我们首先访问根节点,然后递归地遍历左子树和右子树。
- 求深度时,我们关注的是从根节点到达任一叶子节点的最短路径。在前序遍历的过程中,可以很容易地追踪到达当前节点的路径长度,并在到达叶子节点时更新深度(如果当前路径长度小于已知最小深度)。
- 这种遍历方式能够在遍历的过程中及时更新深度信息,不需要等到整棵树遍历完毕。
-
求高度使用后序遍历:
- 在后序遍历中,我们先递归地遍历左子树和右子树,然后访问根节点。
- 求高度时,我们关注的是从根节点到达任一叶子节点的最长路径。在后序遍历的过程中,当我们访问一个节点时,其左右子树已经被完全遍历,因此可以直接获取左右子树的高度。
- 通过比较左右子树的高度,取最大值然后加上当前节点自身的高度(通常为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;//返还给上一层
}
}
前序遍历求深度
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;
}
}
这道题我们虽然求的也是深度,但是用的是后序遍历
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
)。
总结
- 第一种方法在递归过程中对深度进行了显式管理,且总是对左右子树进行递归,即使其中一个为空。
- 第二种方法的递归逻辑更加直观和简洁。它只在子树存在时进行递归,并且在每次递归返回时自然地累加深度。
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());
}
}
}
怎么写一个递归?
- 分解问题:考虑如何将问题分解成更小的部分,并使用相同的函数来解决这些更小的问题。
- 自我调用:在递归案例中,函数应该以新的参数调用自身。