目录
【基础知识】
二叉树的高度和深度
- 高度:当前节点距离叶子节点的距离,通常用后序遍历求高度。
- 深度:当前节点距离根节点的距离,通常用前序遍历求深度。
【104.二叉树的最大深度】
方法一 间接法【求高度,递归 - 后序遍历】
关键:
- 二叉树的最大深度 = 根节点的最大高度
- 父节点的最大高度 = 左右子节点高度的最大值 + 1(需要先访问左右节点,再访问中节点,所以对应的是后序遍历)
思路:
- 确定参数和返回值:目的是获取当前节点的高度,输入的是TreeNode类型的节点,输出int型高度
- 确定终止条件:如果当前节点为null,则返回高度为0(注意:叶子节点的高度为1)
- 确定单层递归逻辑:获取当前节点/父节点的高度,即要获取左右子节点的最大高度再+1
/**
* 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 int maxDepth(TreeNode root) {
// 关键:最大深度 = 根节点的高度,求高度用后序遍历
// 1、确定参数和返回值:目的是获取当前节点的高度,输入的是TreeNode类型的节点,输出int型高度
// 2、确定终止条件:如果当前节点为null,则返回高度为0(注意:叶子节点的高度为1)
if (root == null) return 0;
// 3、确定单层递归逻辑:获取当前节点/父节点的高度,即要获取左右子节点的最大高度再+1
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
}
}
方法二 直接法【求深度,递归 - 前序遍历】
关键:
直接求深度,用前序遍历,子节点的深度 = 父节点的深度 + 1
思路:
1、确定参数和返回值:目的是计算当前节点的深度和当前的最大深度,因此需要传入当前节点和父节点的深度。由于是前序遍历,先获取的当前节点/中节点的深度,再获取左右节点的深度,此时左右节点的深度返回没有意义。因此,参数为TreeNode类的当前节点,int型的父节点深度,返回值为void。
2、确定终止条件:当前节点为null时,计算深度没有意义,直接返回即可
3、确定单层递归逻辑
- 计算中节点/当前节点的最大深度
- 将中节点的深度与当前最大深度比较,更新最大深度
- 计算左节点的深度
- 计算右节点的深度
/**
* 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 {
int depth = 0; // 需要定义为成员变量,便于在递归过程中更新最大深度
public int maxDepth(TreeNode root) {
getDepth(root, 0); // 对于根节点,父节点的深度为0,根节点的深度为1
return depth;
}
// 1、确定参数和返回值:目的是计算当前节点的深度和当前的最大深度,因此需要传入当前节点和父节点的深度
// 由于是前序遍历,先获取的当前节点/中节点的深度,再获取左右节点的深度,此时左右节点的深度返回没有意义
// 因此,参数为TreeNode类的当前节点,int型的父节点深度,返回值为void
public void getDepth(TreeNode root, int fatherDepth){
// 2、确定终止条件:当前节点为null时,计算深度没有意义,直接返回即可
if (root == null) return;
// 3、确定单层递归逻辑:计算中节点的深度,将中节点的深度与当前最大深度比较,更新最大深度,再计算左右节点的深度
// 计算中节点/当前节点的最大深度
int nowDepth = fatherDepth + 1;
depth = Math.max(depth, nowDepth);
// 获取左节点的深度
getDepth(root.left, nowDepth);
// 获取右节点的深度
getDepth(root.right, nowDepth);
}
}
【559.n叉树的最大深度】
方法一 间接法【求高度,递归 - 后序遍历】
思路和104的方法一相同
/*
// Definition for a Node.
class Node {
public int val;
public List<Node> children;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, List<Node> _children) {
val = _val;
children = _children;
}
};
*/
class Solution {
public int maxDepth(Node root) {
// 最大深度 = 根节点的高度,高度用递归的后序遍历,父节点的高度 = 左右子节点的高度最大值 + 1
// 1、确定参数和返回值:输入节点(Node),获取节点的深度(int)
// 2、确定终止条件:如果当前节点为null,高度为0
if (root == null) return 0;
// 3、确定单层逻辑:获取所有子节点的高度的最大值,再+1作为当前节点的高度
int maxHeight = 0;
for (int i = 0; i < root.children.size(); i++){
maxHeight = Math.max(maxHeight, maxDepth(root.children.get(i)));
}
return maxHeight + 1;
}
}
方法二 直接法【求深度,递归 - 前序遍历】
思路和104的方法一相同
/*
// Definition for a Node.
class Node {
public int val;
public List<Node> children;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, List<Node> _children) {
val = _val;
children = _children;
}
};
*/
class Solution {
int maxDepth = 0;
public int maxDepth(Node root) {
// 直接求最大深度,利用前序遍历(中左右)求,子节点的深度 = 父节点的深度 + 1
getMaxDepth(root, 0);
return maxDepth;
}
// 1、确定参数和返回值
public void getMaxDepth(Node root, int fatherDepth){
// 2、确定终止条件
if (root == null) return;
// 3、确定单层递归逻辑
int depth = fatherDepth + 1;
maxDepth = Math.max(maxDepth, depth);
for (int i = 0; i < root.children.size(); i++){
getMaxDepth(root.children.get(i), depth);
}
}
}
【111.二叉树的最小深度】
方法一 递归 - 前序遍历
思路:
- 最小深度:所有叶子节点的深度的最小值
- 如果当前节点是叶子节点,才有机会更新最小深度。
/**
* 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 {
int minDepth = Integer.MAX_VALUE;
public int minDepth(TreeNode root) {
if (root == null) return 0;
getMinDepth(root, 1);
return minDepth;
}
// 1、确定参数值,传入当前节点root和当前节点的深度depth,不需要返回值,更新成员变量minDepth即可
public void getMinDepth(TreeNode root, int depth){
// 2、确定终止条件
if (root == null) return;
// 3、确定单层递归逻辑
// 只有当一个节点没有左右子节点时,才是叶子节点,才需要将当前叶子节点的深度与当前最小深度比较
if (root.left == null && root.right == null){
minDepth = Math.min(minDepth, depth);
}
// 遍历左节点和右节点
getMinDepth(root.left, depth + 1);
getMinDepth(root.right, depth + 1);
}
}
方法二 递归 - 后序遍历
思路:
- 最小深度 = 根节点的最小高度
- 父节点的最小高度 = 左右节点的高度最小值 + 1
注意:
如果左右节点其中一个为null,则不考虑null子节点的高度。例如:
如果考虑null左节点,那么根节点的最小高度就是1,明显是错误的。
/**
* 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 {
// 最小深度 = 根节点的最小高度,后序遍历求高度,父节点的最小高度 = 左右节点高度的最小值 + 1
// 1、确定参数和返回值,参数就是传入当前节点,返回值就是int型的当前节点的最小高度
public int minDepth(TreeNode root) {
// 2、确定终止条件
if (root == null) return 0;
// 3、确定单层递归循环
// 如果左节点为空,则只考虑右节点的高度
if (root.left == null) return minDepth(root.right) + 1;
// 如果右节点为空,则只考虑左节点的高度
if (root.right == null) return minDepth(root.left) + 1;
// 如果左右节点都不为空,则取左右节点高度的最小值 + 1 作为当前节点的最小高度
return Math.min(minDepth(root.left), minDepth(root.right)) + 1;
}
}
【222.完全二叉树的节点个数】
方法一 递归 - 按照普通二叉树的规则
思路1:递归遍历所有节点,每遍历到一个节点就使num+1
/**
* 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 {
int num = 0;
public int countNodes(TreeNode root) {
getNum(root);
return num;
}
// 1、确定参数和返回值:传入当前节点,不需要返回,在遍历到当前节点的时候num++即可
public void getNum(TreeNode root){
// 2、确定终止条件
if (root == null) return;
// 3、确定单层递归逻辑
num++;
getNum(root.left);
getNum(root.right);
}
}
思路2:当前节点对应子树的所有节点数 = 左右节点对应子树的所有节点数之和 + 1
/**
* 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 {
// 1、确定参数和返回值,该函数的作用是返回当前节点对应子树的所有节点数量
public int countNodes(TreeNode root) {
// 2、确定终止条件
if (root == null) return 0;
// 3、确定单层递归逻辑
return countNodes(root.left) + countNodes(root.right) + 1;
}
}
两种思想的复杂度一致:
时间复杂度:O(n) ,有可能是满二叉树,相当于遍历一次所有节点
空间复杂度:O(n),取决于递归的次数,即相当于遍历一次所有节点
方法二 递归 - 针对完全二叉树的性质
完全二叉树:从上到下,从左到右,节点都是不间断的,而且只有最后一层缺少节点的树。
思路:判断该节点对应的子树是否为满二叉树
判断方法:只向左遍历的深度 = 只向右遍历的深度,则该树为满二叉树
- 如果是,则根据2^k - 1计算该节点对应子树的所有节点数
- 如果不是,继续遍历左节点和右节点,当前节点对应子树的所有节点数 = 左右子树所有节点之和 + 1
/**
* 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 {
// 判断该节点对应的树是否为满二叉树,是的话就返回该二叉树的深度,否的话就返回-1
public int isFull(TreeNode root){
// 计算只向左遍历的深度
TreeNode tmp = root;
int leftDepth = 0;
while(tmp != null){
leftDepth++;
tmp = tmp.left;
}
// 计算只向右遍历的深度
tmp = root;
int rightDepth = 0;
while(tmp != null){
rightDepth++;
tmp = tmp.right;
}
return leftDepth == rightDepth ? leftDepth : -1;
}
// 1、确定参数和返回值,该函数的作用是返回当前节点对应子树的所有节点数量
public int countNodes(TreeNode root) {
// 2、确定终止条件
if (root == null) return 0;
// 3、确定单层递归逻辑
// 判断该节点对应的子树是否为满二叉树,
int k = isFull(root);
// - 如果是,则根据2^k - 1计算该节点对应子树的所有节点数
if (k != -1){
return (int) Math.pow(2, k) - 1; // 注意Math.pow返回的是double型数据,要进行强转才能-1
}
// - 如果不是,继续遍历左节点和右节点
else{
int leftNum = countNodes(root.left);
int rightNum = countNodes(root.right);
return leftNum + rightNum + 1;
}
}
}
时间复杂度:O(logn × logn) ,判断O(logn),访问左右子树也是O(logn),访问后要判断即要相乘
空间复杂度:O(logn),取决于递归的深度,完全二叉树的递归深度为 O(log n)