Bootstrap

代码随想录训练第十三天|LeetCode110.平衡二叉树、LeetCode257.二叉树的所有路径、LeetCode404.左叶子之和、LeetCode222.完全二叉树的节点个数

110.平衡二叉树

给定一个二叉树,判断它是否是 平衡二叉树

一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。

示例 1:

img

输入:root = [3,9,20,null,null,15,7]
输出:true

示例 2:

img

输入:root = [1,2,2,3,3,null,null,4,4]
输出:false

示例 3:

输入:root = []
输出:true

提示:

  • 树中的节点数在范围 [0, 5000]
  • -104 <= Node.val <= 104

Related Topics

  • 深度优先搜索

  • 二叉树

思路

咋眼一看这道题目和104.二叉树的最大深度 (opens new window)很像,其实有很大区别。

这里强调一波概念:

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

但leetcode中强调的深度和高度很明显是按照节点来计算的,如图:

110.平衡二叉树2

关于根节点的深度究竟是1 还是 0,不同的地方有不一样的标准,leetcode的题目中都是以节点为一度,即根节点深度是1。但维基百科上定义用边为一度,即根节点的深度是0,我们暂时以leetcode为准(毕竟要在这上面刷题)。

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

有的同学一定疑惑,为什么104.二叉树的最大深度 (opens new window)中求的是二叉树的最大深度,也用的是后序遍历。

那是因为代码的逻辑其实是求的根节点的高度,而根节点的高度就是这棵树的最大深度,所以才可以使用后序遍历。

递归法

  1. 明确递归函数的参数和返回值

参数:当前传入节点。 返回值:以当前传入节点为根节点的树的高度。

那么如何标记左右子树是否差值大于1呢?

如果当前传入节点为根节点的二叉树已经不是二叉平衡树了,还返回高度的话就没有意义了。

所以如果已经不是二叉平衡树了,可以返回-1 来标记已经不符合平衡树的规则了。

代码如下:

private int getHeight(TreeNode root) {
    
}
  1. 明确终止条件

递归的过程中依然是遇到空节点了为终止,返回0,表示当前节点为根节点的树高度为0

 if (root == null) {
        return 0;
    }
  1. 明确单层递归的逻辑

如何判断以当前传入节点为根节点的二叉树是否是平衡二叉树呢?当然是其左子树高度和其右子树高度的差值。

分别求出其左右子树的高度,然后如果差值小于等于1,则返回当前二叉树的高度,否则返回-1,表示已经不是二叉平衡树了。

代码如下:

    //获得左子树的高度
    int leftDepth = getHeight(root.left);
    //获得左子树的高度
    int rightDepth = getHeight(root.right);
    //如果左子树或者右子树的左右叶子节点出现了高度差大于-1时.返回-1
    if (leftDepth == -1 || rightDepth == -1) {
        return -1;
    }
    //如果左节点与右节点高度差大于1时,返回-1;
    if (Math.abs(leftDepth - rightDepth) > 1) {  // 中
        return -1;
    } else {
        //返回以当前节点为根节点的树的最大高度
        return 1+Math.max(leftDepth,rightDepth);
    }

完全的递归遍历如下

public boolean isBalanced(TreeNode root) {
    int res = getHeight(root);
    return res == -1 ? false : true;
}

/**
 * @Description 获得当前节点左右子树高度差
 * @Param
 * @Return {@link int}
 * @Author 君君
 * @Date 2024/7/5 14:33
 */
private int getHeight(TreeNode root) {
    if (root == null) {
        return 0;
    }
    //获得左子树的高度
    int leftDepth = getHeight(root.left);
    //获得左子树的高度
    int rightDepth = getHeight(root.right);
    //如果左子树或者右子树的左右叶子节点出现了高度差大于-1时.返回-1
    if (leftDepth == -1 || rightDepth == -1) {
        return -1;
    }
    //如果左节点与右节点高度差大于1时,返回-1;
    if (Math.abs(leftDepth - rightDepth) > 1) {  // 中
        return -1;
    } else {
        //返回以当前节点为根节点的树的最大高度
        return 1+Math.max(leftDepth,rightDepth);
    }
}

迭代法

104.二叉树的最大深度 (opens new window)中我们可以使用层序遍历来求深度,但是就不能直接用层序遍历来求高度了,这就体现出求高度和求深度的不同。

本题的迭代方式可以先定义一个函数,专门用来求高度。

这个函数通过栈模拟的后序遍历找每一个节点的高度(其实是通过求传入节点为根节点的最大深度来求的高度)

cur节点的最大深度,就是cur的高度

 		/**
         * 层序遍历,求结点的高度
         */
        public int getHeight(TreeNode root) {
            if (root == null) {
                return 0;
            }
            Deque<TreeNode> deque = new LinkedList<>();
            deque.offer(root);
            int depth = 0;
            while (!deque.isEmpty()) {
                int size = deque.size();
                depth++;
                for (int i = 0; i < size; i++) {
                    TreeNode poll = deque.poll();
                    if (poll.left != null) {
                        deque.offer(poll.left);
                    }
                    if (poll.right != null) {
                        deque.offer(poll.right);
                    }
                }
            }
            return depth;
        }

然后再用栈来模拟后序遍历,遍历每一个节点的时候,再去判断左右孩子的高度是否符合,代码如下:

 		/**
         * @Description 迭代法(后序遍历,遍历每一个节点,获得每一个节点的深度)
         * @Param root
         * @Return {@link boolean}
         * @Author 君君
         * @Date 2024/7/5 14:57
         */
        public boolean isBalanced(TreeNode root) {
            if (root == null)
                return true;
            Stack<TreeNode> stack = new Stack<>();
            stack.push(root);
            while(!stack.isEmpty())
            {
                TreeNode node = stack.peek();                       // 中
                if(node!=null)
                {
                    stack.pop();
                    stack.push(node);
                    stack.push(null);
                    if (Math.abs(getHeight(node.left) - getHeight(node.right)) > 1) {
                        return false;
                    }
                    if (node.right!=null) stack.push(node.right);           // 右(空节点不入栈)
                    if (node.left!=null) stack.push(node.left);             // 左(空节点不入栈)
                }
                else{
                    stack.pop();
                    stack.pop();
                }

            }
            return true;
        }

整体代码如下:

	/**
         * 层序遍历,求结点的高度
         */
        public int getHeight(TreeNode root) {
            if (root == null) {
                return 0;
            }
            Deque<TreeNode> deque = new LinkedList<>();
            deque.offer(root);
            int depth = 0;
            while (!deque.isEmpty()) {
                int size = deque.size();
                depth++;
                for (int i = 0; i < size; i++) {
                    TreeNode poll = deque.poll();
                    if (poll.left != null) {
                        deque.offer(poll.left);
                    }
                    if (poll.right != null) {
                        deque.offer(poll.right);
                    }
                }
            }
            return depth;
        }
 		/**
         * @Description 迭代法(后序遍历,遍历每一个节点,获得每一个节点的深度)
         * @Param root
         * @Return {@link boolean}
         * @Author 君君
         * @Date 2024/7/5 14:57
         */
        public boolean isBalanced(TreeNode root) {
            if (root == null)
                return true;
            Stack<TreeNode> stack = new Stack<>();
            stack.push(root);
            while(!stack.isEmpty())
            {
                TreeNode node = stack.peek();                       // 中
                if(node!=null)
                {
                    stack.pop();
                    stack.push(node);
                    stack.push(null);
                    if (Math.abs(getHeight(node.left) - getHeight(node.right)) > 1) {
                        return false;
                    }
                    if (node.right!=null) stack.push(node.right);           // 右(空节点不入栈)
                    if (node.left!=null) stack.push(node.left);             // 左(空节点不入栈)
                }
                else{
                    stack.pop();
                    stack.pop();
                }

            }
            return true;
        }

时间复杂度O(N^2)

空间复杂度O(N)

当然此题用迭代法,其实效率很低,因为没有很好的模拟回溯的过程,所以迭代法有很多重复的计算。

虽然理论上所有的递归都可以用迭代来实现,但是有的场景难度可能比较大。

例如:都知道回溯法其实就是递归,但是很少人用迭代的方式去实现回溯算法!

因为对于回溯算法已经是非常复杂的递归了,如果再用迭代的话,就是自己给自己找麻烦,效率也并不一定高。

257.二叉树的所有路径

给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。

叶子节点 是指没有子节点的节点。

示例 1:

img

输入:root = [1,2,3,null,5]
输出:["1->2->5","1->3"]

示例 2:

输入:root = [1]
输出:["1"]

提示:

  • 树中节点的数目在范围 [1, 100]
  • -100 <= Node.val <= 100

Related Topics

  • 深度优先搜索
  • 字符串
  • 回溯
  • 二叉树

递归法

  1. 递归函数参数以及返回值

要传入根节点,记录每一条路径的path,和存放结果集的res,这里递归不需要返回值,代码如下:

private void traversal(TreeNode root,List<Integer> paths,List<String> res){
    ...
}
  1. 确定递归终止条件

在写递归的时候都习惯了这么写:

if (cur == NULL) {
    终止处理逻辑
}

但是本题的终止条件这样写会很麻烦,因为本题要找到叶子节点,就开始结束的处理逻辑了(把路径放进result里)。

那么什么时候算是找到了叶子节点? 是当 cur不为空,其左右孩子都为空的时候,就找到叶子节点。

所以本题的终止条件是:

if(root.left==null&&root.right==null){
    StringBuilder sb = new StringBuilder();// StringBuilder用来拼接字符串,速度更快
			for (int i = 0; i < paths.size() - 1; i++) {
				sb.append(paths.get(i)).append("->");
			}
			sb.append(paths.get(paths.size() - 1));
			res.add(sb.toString());
			return;
}
  1. 确定单层递归逻辑

因为是前序遍历,需要先处理中间节点,中间节点就是我们要记录路径上的节点,先放进path中。

		//已经确定当前节点不会为空
		paths.add(root.val);

然后是递归和回溯的过程,上面说过没有判断cur是否为空,那么在这里递归的时候,如果为空就不进行下一层递归了。

所以递归前要加上判断语句,下面要递归的节点是否为空,

此时还没完,递归完,要做回溯啊,因为path 不能一直加入节点,它还要删节点,然后才能加入新的节点。

		//向左边遍历
		if(root.left!=null)
		{
			//此时向左递归
			traversal(root.left,paths,res);
			//向左递归已经执行完毕,进行回溯
			paths.remove(paths.size()-1);
		}
		//向右边遍历
		if(root.right!=null)
		{
			//此时向右递归
			traversal(root.right,paths,res);
			//向右递归已经执行完毕,进行回溯
			paths.remove(paths.size()-1);
		}

回溯和递归是一一对应的,有一个递归,就要有一个回溯,这么写的话相当于把递归和回溯拆开了, 一个在花括号里,一个在花括号外。

所以回溯要和递归永远在一起,世界上最遥远的距离是你在花括号里,而我在花括号外!

整体代码如下

    public List<String> binaryTreePaths(TreeNode root) {
		List<String> res = new ArrayList<>();
		if (root == null) {
			return res;
		}
		List<Integer> paths = new ArrayList<>();// 作为结果中的路径
		traversal(root,paths,res);
		return res;
    }

	/**
	 * @Description 使用递归法+回溯进行路径的记录
	 * @Param root 当前节点
	 * @Param paths 当前路径,当回溯到上一个节点时,要将paths的最后一个数字去掉
	 * @Param res 结果值,只有在找到目标值时才进行操作
	 * @Return
	 * @Author 君君
	 * @Date 2024/7/6 3:18
	 */
	private void traversal(TreeNode root,List<Integer> paths,List<String> res){
		//已经确定当前节点不会为空
		paths.add(root.val);
		//终止条件,已经找到了无叶子节点
		if(root.left==null&&root.right==null){
			StringBuilder sb = new StringBuilder();// StringBuilder用来拼接字符串,速度更快
			for (int i = 0; i < paths.size() - 1; i++) {
				sb.append(paths.get(i)).append("->");
			}
			sb.append(paths.get(paths.size() - 1));
			res.add(sb.toString());
			return;
		}
		//向左边遍历
		if(root.left!=null)
		{
			//此时向左递归
			traversal(root.left,paths,res);
			//向左递归已经执行完毕,进行回溯
			paths.remove(paths.size()-1);
		}
		//向右边遍历
		if(root.right!=null)
		{
			//此时向右递归
			traversal(root.right,paths,res);
			//向右递归已经执行完毕,进行回溯
			paths.remove(paths.size()-1);
		}
	}

时间复杂度O(N)

空间复杂度O(N)

迭代法

	/**
	 * @Description 迭代法
	 * @Param root
	 * @Return {@link List< String>}
	 * @Author 君君
	 * @Date 2024/7/6 3:36
	 */
	public List<String> binaryTreePaths(TreeNode root){
		List<String> res = new ArrayList<>();
		if(root==null) {
			return res;
		}
		Stack<String> paths = new Stack<>();
		paths.push(root.val+"");
		Stack<TreeNode> stack = new Stack<>();
		stack.push(root);
		while(!stack.isEmpty()&&!paths.isEmpty()){
			//获得当前路径
			String path = paths.pop();
			//得到当前节点
			TreeNode temp =stack.pop();

			//找到叶子节点,将当前路径加入到结果集中
			if(temp.left==null&&temp.right==null) {
				res.add(path);
			}
			//将右节点入栈(不为空)
			if(temp.right!=null) {
				//paths.
				stack.push(temp.right);
				paths.push(path + "->" + temp.right.val);
			}
			//将左节点入栈(不为空)
			if(temp.left!=null) {
				stack.push(temp.left);
				paths.push(path + "->" + temp.left.val);
			}
		}
		return res;
	}

时间复杂度O(N)

空间复杂度O(N)

404.左叶子之和

给定二叉树的根节点 root ,返回所有左叶子之和。

示例 1:

img

输入: root = [3,9,20,null,null,15,7] 
输出: 24 
解释: 在这个二叉树中,有两个左叶子,分别是 9 和 15,所以返回 24

示例 2:

输入: root = [1]
输出: 0

提示:

  • 节点数在 [1, 1000] 范围内
  • -1000 <= Node.val <= 1000

Related Topics

  • 深度优先搜索
  • 广度优先搜索
  • 二叉树

思路

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

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

大家思考一下如下图中二叉树,左叶子之和究竟是多少?

404.左叶子之和

其实是0,因为这棵树根本没有左叶子!

但看这个图的左叶子之和是多少?

但看这个图的左叶子之和是多少?

图二

相信通过这两个图,大家对最左叶子的定义有明确理解了。

那么判断当前节点是不是左叶子是无法判断的,必须要通过节点的父节点来判断其左孩子是不是左叶子。

如果该节点的左节点不为空,该节点的左节点的左节点为空,该节点的左节点的右节点为空,则找到了一个左叶子

if (node->left != NULL && node->left->left == NULL && node->left->right == NULL) {
    左叶子节点处理逻辑
}

递归法

递归的遍历顺序为后序遍历(左右中),是因为要通过递归函数的返回值来累加求取左叶子数值之和。

递归三部曲:

  1. 确定递归函数的参数和返回值

判断一个树的左叶子节点之和,那么一定要传入树的根节点,递归函数的返回值为数值之和,所以为int

使用题目中给出的函数就可以了。

private int sum(TreeNode node){
    
}
  1. 确定终止条件

如果遍历到空节点,那么左叶子值一定是0

		if(node==null)
		{
			return 0;
		}

注意,只有当前遍历的节点是父节点,才能判断其子节点是不是左叶子。 所以如果当前遍历的节点是叶子节点,那其左叶子也必定是0,那么终止条件为:

		if(node==null)
		{
			return 0;
		}
		//其实这个也可以不写,如果不写不影响结果,但就会让递归多进行了一层。
		if (node.left == null && node.right== null)
			return 0;
  1. 确定单层递归的逻辑

当遇到左叶子节点的时候,记录数值,然后通过递归求取左子树左叶子之和,和 右子树左叶子之和,相加便是整个树的左叶子之和。

		//求左子树
		int leftValue = sumOfLeftLeaves(node.left);
		//此时左子树就是一个左叶子
		if(node.left!=null&&node.left.left==null&&node.left.right==null){
			leftValue = node.left.val;
		}
		//求右子树
		int rightValue = sumOfLeftLeaves(node.right);
		int sum = leftValue+rightValue;
		return sum;

整体代码

	/**
	 * @Description 求当前节点的左
	 * @Param node
	 * @Return {@link int}
	 * @Author 君君
	 * @Date 2024/7/6 4:23
	 */
	private int sum(TreeNode node)
	{
		if(node==null)
		{
			return 0;
		}
		//其实这个也可以不写,如果不写不影响结果,但就会让递归多进行了一层。
		if (node.left == null && node.right== null)
			return 0;

		//求左子树
		int leftValue = sumOfLeftLeaves(node.left);
		//此时左子树就是一个左叶子
		if(node.left!=null&&node.left.left==null&&node.left.right==null){
			leftValue = node.left.val;
		}
		//求右子树
		int rightValue = sumOfLeftLeaves(node.right);
		int sum = leftValue+rightValue;
		return sum;
	}

时间复杂度O(N)

空间复杂度O(N)

迭代法

本题迭代法使用前中后序都是可以的,只要把左叶子节点统计出来,就可以了

	/**
	 * @Description 迭代法
	 * @Param root
	 * @Return {@link int}
	 * @Author 君君
	 * @Date 2024/7/6 4:34
	 */
	public int sumOfLeftLeaves(TreeNode root) {
		int res = 0;
		if (root == null) return 0;
		Stack<TreeNode> stack = new Stack<> ();
		stack.push(root);
		while(!stack.isEmpty())
		{
			TreeNode temp=stack.pop();
			//找到叶子节点,对res进行处理
			if (temp.left != null && temp.left.left == null && temp.left.right == null) {
				res += temp.left.val;
			}
			if (temp.right != null) stack.add(temp.right);
			if (temp.left != null) stack.add(temp.left);
		}
		return res;
	}

时间复杂度O(N)

空间复杂度O(N)

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

给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。

完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2h 个节点。

示例 1:

img

输入:root = [1,2,3,4,5,6]
输出:6

示例 2:

输入:root = []
输出:0

示例 3:

输入:root = [1]
输出:1

提示:

  • 树中节点的数目范围是[0, 5 * 104]
  • 0 <= Node.val <= 5 * 104
  • 题目数据保证输入的树是 完全二叉树

**进阶:**遍历树来统计节点是一种时间复杂度为 O(n) 的简单解决方案。你可以设计一个更快的算法吗?

Related Topics

  • 位运算
  • 二分查找
  • 二叉树

思路

本篇给出按照普通二叉树的求法以及利用完全二叉树性质的求法。

完全二叉树的节点个数.html#普通二叉树)普通二叉树

首先按照普通二叉树的逻辑来求。

这道题目的递归法和求二叉树的深度写法类似, 而迭代法,二叉树:层序遍历登场! (opens new window)遍历模板稍稍修改一下,记录遍历的节点数量就可以了。

递归遍历的顺序依然是后序(左右中)。

递归

  1. 确定递归函数的参数和返回值:参数就是传入树的根节点,返回就返回以该节点为根节点二叉树的节点数量,所以返回值为int类型。
private int getNodesNum(TreeNode node){
    
}
  1. 确定终止条件:如果为空节点的话,就返回0,表示节点数为0。
    if (node == null) {
        return 0;
    }
  1. 确定单层递归的逻辑:先求它的左子树的节点数量,再求右子树的节点数量,最后取总和再加一 (加1是因为算上当前中间节点)就是目前节点为根节点的节点数量。
    int leftNum = getNodesNum(node.left);
    int rightNum = getNodesNum(node.right);
    //当前节点的所有节点数
    int sum = leftNum + rightNum + 1;
    return sum;

完整代码

public int countNodes(TreeNode root) {
    return getNodesNum(root);
}

/**
 * @Description 传入当前节点, 返回当前节点下边的节点数
 * @Param node
 * @Return {@link int}
 * @Author 君君
 * @Date 2024/7/6 4:41
 */
private int getNodesNum(TreeNode node) {
    if (node == null) {
        return 0;
    }
    int leftNum = getNodesNum(node.left);
    int rightNum = getNodesNum(node.right);
    //当前节点的所有节点数
    int sum = leftNum + rightNum + 1;
    return sum;
}

时间复杂度:O(n)

空间复杂度:O(log n),算上了递归系统栈占用的空间

迭代法

那么只要模板少做改动,加一个变量result,统计节点数量就可以了

/**
 * @Description 迭代法(层序遍历)
 * @Param root
 * @Return {@link int}
 * @Author 君君
 * @Date 2024/7/6 4:53
 */
public int countNodes(TreeNode root) {
    int res = 0;
    if(root==null)
    {
        return res;
    }
    Deque<TreeNode> deque = new LinkedList<>();
    deque.offer(root);
    while(!deque.isEmpty())
    {
        int size = deque.size();
        for (int i = 0; i < size; i++) {
            TreeNode temp = deque.poll();
            res++;
            if(temp.right!=null)
            {
                deque.offer(temp.right);
            }
            if(temp.left!=null)
            {
                deque.offer(temp.left);
            }
        }
    }
    return res;

}

时间复杂度:O(n)

空间复杂度:O(n)

完全二叉树

以上方法都是按照普通二叉树来做的,对于完全二叉树特性不了解的同学可以看这篇 关于二叉树,你该了解这些! (opens new window),这篇详细介绍了各种二叉树的特性。

在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2^(h-1) 个节点。

大家要自己看完全二叉树的定义,很多同学对完全二叉树其实不是真正的懂了。

我来举一个典型的例子如题:

img

完全二叉树只有两种情况,情况一:就是满二叉树,情况二:最后一层叶子节点没有满。

对于情况一,可以直接用 2^树深度 - 1 来计算,注意这里根节点深度为1。

对于情况二,分别递归左孩子,和右孩子,递归到某一深度一定会有左孩子或者右孩子为满二叉树,然后依然可以按照情况1来计算。

完全二叉树(一)如图: 222.完全二叉树的节点个数

完全二叉树(二)如图: 222.完全二叉树的节点个数1

可以看出如果整个树不是满二叉树,就递归其左右孩子,直到遇到满二叉树为止,用公式计算这个子树(满二叉树)的节点数量。

这里关键在于如何去判断一个左子树或者右子树是不是满二叉树呢?

在完全二叉树中,如果递归向左遍历的深度等于递归向右遍历的深度,那说明就是满二叉树。如图:

img

在完全二叉树中,如果递归向左遍历的深度不等于递归向右遍历的深度,则说明不是满二叉树,如图:

img

那有录友说了,这种情况,递归向左遍历的深度等于递归向右遍历的深度,但也不是满二叉树,如题:

img

如果这么想,大家就是对 完全二叉树理解有误区了,以上这棵二叉树,它根本就不是一个完全二叉树

判断其子树是不是满二叉树,如果是则利用公式计算这个子树(满二叉树)的节点数量,如果不是则继续递归,那么 在递归三部曲中,第二部:终止条件的写法应该是这样的:

if (root == null) return 0;
    TreeNode left = root.left;
    TreeNode right = root.right;
    int leftDepth = 0;
    int rightDepth = 0;
    //求左子树深度
    while (left != null) {
        left = left.left;
        leftDepth++;
    }
    //求右子树深度
    while (right != null) {
        right = right.right;
        rightDepth++;
    }
    //此时为满二叉树
    if (leftDepth == rightDepth) {
        // 注意(2<<1) 相当于2^2,所以leftDepth初始为0
        return (2 << leftDepth) - 1;
    }

递归三部曲,第三部,单层递归的逻辑:(可以看出使用后序遍历)

int leftTreeNum = countNodes(root.left);       // 左
int rightTreeNum = countNodes(root.right);     // 右
int result = leftTreeNum + rightTreeNum + 1;    // 中
return result;

该部分精简之后代码为:

return countNodes(root.left) + countNodes(root.right) + 1;
/**
 * @Description 递归法(完全二叉树)
 * @Param root
 * @Return {@link int}
 * @Author 君君
 * @Date 2024/7/6 5:02
 */
public int countNodes(TreeNode root) {
    if (root == null) return 0;
    TreeNode left = root.left;
    TreeNode right = root.right;
    int leftDepth = 0;
    int rightDepth = 0;
    //求左子树深度
    while (left != null) {
        left = left.left;
        leftDepth++;
    }
    //求右子树深度
    while (right != null) {
        right = right.right;
        rightDepth++;
    }
    //此时为满二叉树
    if (leftDepth == rightDepth) {
        // 注意(2<<1) 相当于2^2,所以leftDepth初始为0
        return (2 << leftDepth) - 1;
    }
    //如果不是满二叉树,那么,返回的就是左右子树节点的和
    return countNodes(root.left) + countNodes(root.right) + 1;
}

时间复杂度:O(log n × log n)

空间复杂度:O(log n)

;