104.二叉树的最大深度
给定一个二叉树,找出其最大深度。
示例: 给定二叉树 [3,9,20,null,null,15,7],返回它的最大深度 3 。
递归法
- 二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数或者节点数(取决于深度从0开始还是从1开始)
- 二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数或者节点数(取决于高度从0开始还是从1开始)
可以使用前序(中左右),也可以使用后序遍历(左右中),使用前序求的就是深度,使用后序求的是高度。
后序方法:根节点的高度就是二叉树的最大深度
- 确定递归函数的参数和返回值:参数就是传入树的根节点,返回就返回这棵树的深度,所以返回值为int类型。
int maxDepth(struct TreeNode* root)
- 确定终止条件:如果为空节点的话,就返回0,表示高度为0。
if(!root) return 0;
- 确定单层递归的逻辑:先求它的左子树的深度,再求右子树的深度,最后取左右深度最大的数值 再+1 (加1是因为算上当前中间节点)就是目前节点为根节点的树的深度。
int left = maxDepth(root->left); int right = maxDepth(root->right); int max = left > right ? left : right; return max + 1;
完整代码如下:
int maxDepth(struct TreeNode* root){
//若传入结点为NULL,返回0
if(!root)
return 0;
//求出左子树深度
int left = maxDepth(root->left);
//求出右子树深度
int right = maxDepth(root->right);
//求出左子树深度和右子树深度的较大值
int max = left > right ? left : right;
//返回较大值+1(1为当前层数)
return max + 1;
}
精简代码
int maxDepth(struct TreeNode* root) {
return root ? 1 + fmax(maxDepth(root->left), maxDepth(root->right)) : 0;
}
前序方法:(充分表现出求深度回溯的过程)
#include <stdio.h>
#include <stdlib.h>
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
};
int result;
void getDepth(struct TreeNode* node, int depth) {
result = depth > result ? depth : result;//中
if (node->left == NULL && node->right == NULL) return;
if (node->left) {//左
getDepth(node->left, depth + 1);//深度+1
}
if (node->right) {//右
getDepth(node->right, depth + 1);//深度+1
}
return;
}
int maxDepth(struct TreeNode* root) {
result = 0;
if (root == NULL) return result;
getDepth(root, 1);
return result;
}
java写法
class solution {
/**
* 递归法
*/
public int maxDepth(TreeNode root) {
if (root == null) {
return 0;
}
int leftDepth = maxDepth(root.left);
int rightDepth = maxDepth(root.right);
return Math.max(leftDepth, rightDepth) + 1;
}
}
class Solution {
/**
* 递归法(求深度法)
*/
//定义最大深度
int maxnum = 0;
public int maxDepth(TreeNode root) {
ans(root,0);
return maxnum;
}
//递归求解最大深度
void ans(TreeNode tr,int tmp){
if(tr==null) return;
tmp++;
maxnum = maxnum<tmp?tmp:maxnum;
ans(tr.left,tmp);
ans(tr.right,tmp);
tmp--;
}
}
迭代法
使用迭代法,使用层序遍历合适,最大的深度就是二叉树的层数,和层序遍历的方式极其吻合。
在二叉树中,一层一层的来遍历二叉树,记录一下遍历的层数就是二叉树的深度,如图所示:
int maxDepth(struct TreeNode* root){
//若传入根节点为NULL,返回0
if(!root)
return 0;
int depth = 0;
//开辟队列空间
struct TreeNode** queue = (struct TreeNode**)malloc(sizeof(struct TreeNode*) * 6000);
int queueFront = 0;
int queueEnd = 0;
//将根结点入队
queue[queueEnd++] = root;
int queueSize;
//求出当前队列中元素个数
while(queueSize = queueEnd - queueFront) {
int i;
//若当前队列中结点有左右子树,则将它们的左右子树入队
for(i = 0; i < queueSize; i++) {
struct TreeNode* tempNode = queue[queueFront + i];
if(tempNode->left)
queue[queueEnd++] = tempNode->left;
if(tempNode->right)
queue[queueEnd++] = tempNode->right;
}
//更新队头下标
queueFront += queueSize;
//深度+1
depth++;
}
return depth;
}
java写法
class solution {
/**
* 迭代法,使用层序遍历
*/
public int maxDepth(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 node = deque.poll();
if (node.left != null) {
deque.offer(node.left);
}
if (node.right != null) {
deque.offer(node.right);
}
}
}
return depth;
}
}
559.n叉树的最大深度
给定一个 n 叉树,找到其最大深度。
最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。
递归法
深度优先搜索(DFS)
基本思路是通过递归地访问每个节点的子节点,然后比较子节点的深度,找出最大深度并返回。
int maxDepth(struct Node* root) {
if(root == NULL){//检查传入的节点是否为空,如果为空则返回深度0
return 0;
}
if(root -> numChildren == 0){//检查节点是否为叶子节点(没有子节点),如果是则返回深度1
return 1;
}
int max = 0;
//对于有子节点的节点,函数会递归地调用自身来计算每个子节点的最大深度,然后取其中最大的深度值加1作为当前节点的深度,并返回给上一层递归调用。
for(int i = 0; i < root -> numChildren; i++){
max = fmax(max,maxDepth(root -> children[i]));
}
return 1 + max;
}
java写法
class Solution {
/*递归法,后序遍历求root节点的高度*/
public int maxDepth(Node root) {
if (root == null) return 0;
int depth = 0;
if (root.children != null){
for (Node child : root.children){
depth = Math.max(depth, maxDepth(child));
}
}
return depth + 1; //中节点
}
}
迭代法
广度优先搜索(BFS)
通过维护一个队列来按层级遍历N叉树,从根节点开始,逐层将子节点加入队列中,并在遍历完每一层后增加深度计数器。
这种方法不同于递归,它通过迭代实现,避免了递归调用可能带来的堆栈溢出问题,同时也利用了队列先进先出的特性来确保按层级顺序访问节点。
这种方法在效率上通常优于递归实现,特别是对于大型N叉树。
#define MAX_NUM (10000)//创建了一个指针数组作为队列,用于存储待遍历的节点。
int maxDepth(struct Node* root)
{
if(NULL == root) // 如果根节点为空,返回深度0
{
return 0;
}
struct Node **queue = malloc(sizeof(struct Node *)*MAX_NUM);
struct Node *node = NULL;
int front = 0, rear = 0; //利用front和rear两个指针来标识队头和队尾位置
int tmpPos = 0;// 记录下一层节点的起始位置
int depth = 0; // 记录树的深度
queue[rear++] = root;//初始时将根节点加入队列
while(front < rear)// 当队列不为空时循环
{
tmpPos = rear;// 记录下一层节点的起始位置
while(front < tmpPos)// 循环处理当前层的节点
{
node = queue[front++];// 取出队头节点并向后移动front指针
for(int j=0; j<node->numChildren; j++)// 遍历当前节点的子节点
{
queue[rear++] = node->children[j];// 将子节点加入队列
}
}
depth++; // 当前层节点遍历结束,深度加1
}
return depth;//返回最大深度
}
java写法
class solution {
/**
* 迭代法,使用层序遍历
*/
public int maxDepth(Node root) {
if (root == null) return 0;
int depth = 0;
Queue<Node> que = new LinkedList<>();
que.offer(root);
while (!que.isEmpty())
{
depth ++;
int len = que.size();
while (len > 0)
{
Node node = que.poll();
for (int i = 0; i < node.children.size(); i++)
if (node.children.get(i) != null)
que.offer(node.children.get(i));
len--;
}
}
return depth;
}
}
111.二叉树的最小深度
给定一个二叉树,找出其最小深度。
在处理节点的过程中,最小深度是从根节点到最近叶子节点的最短路径上的节点数量:
如果左子树为空,右子树不为空,最小深度是 1 + 右子树的深度。反之,右子树为空,左子树不为空,最小深度是 1 + 左子树的深度。 最后如果左右子树都不为空,返回左右子树深度最小值 + 1。
求二叉树的最小深度和求二叉树的最大深度的差别主要在于处理左右孩子不为空的逻辑。
递归法
int minDepth(struct TreeNode* root) {
// 如果根节点为空,返回深度为 0
if (root == NULL) {
return 0;
}
// 如果根节点没有左右子树,返回深度为 1
if (root->left == NULL && root->right == NULL) {
return 1;
}
// 递归计算左子树和右子树的最小深度
int leftDepth = minDepth(root->left);
int rightDepth = minDepth(root->right);
// 根据左右子树的情况判断最小深度
if (leftDepth == 0) {
return rightDepth + 1;
} else if (rightDepth == 0) {
return leftDepth + 1;
} else {
return leftDepth < rightDepth ? leftDepth + 1 : rightDepth + 1;
}
}
java写法
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;
}
}
class Solution {
/**
* 递归法(思路来自二叉树最大深度的递归法)
* 该题求最小深度,最小深度为根节点到叶子节点的深度,所以在迭代到每个叶子节点时更新最小值。
*/
int depth = 0;
// 定义最小深度,初始化最大值
int minDepth = Integer.MAX_VALUE;
public int minDepth(TreeNode root) {
dep(root);
return minDepth == Integer.MAX_VALUE ? 0 : minDepth;
}
void dep(TreeNode root){
if(root == null) return ;
// 递归开始,深度增加
depth++;
dep(root.left);
dep(root.right);
// 该位置表示递归到叶子节点了,需要更新最小深度minDepth
if(root.left == null && root.right == null)
minDepth = Math.min(minDepth , depth);
// 递归结束,深度减小
depth--;
}
}
迭代法
只有当左右孩子都为空的时候,才是遍历到最低点了。如果其中一个孩子不为空,则不是最低点。
// 计算二叉树的最小深度
int minDepth(struct TreeNode* root) {
if (root == NULL) {
return 0; // 如果根节点为空,深度为0
}
int depth = 1; // 初始深度为1
// 定义队列节点结构体
struct QueueNode {
struct TreeNode* node;
struct QueueNode* next;
};
typedef struct QueueNode QueueNode;
// 定义队列结构体
struct Queue {
QueueNode* front;
QueueNode* rear;
};
typedef struct Queue Queue;
// 初始化队列
Queue* que = (Queue*)malloc(sizeof(Queue));
que->front = NULL;
que->rear = NULL;
// 将根节点加入队列
QueueNode* newNode = (QueueNode*)malloc(sizeof(QueueNode));
newNode->node = root;
newNode->next = NULL;
que->front = newNode;
que->rear = newNode;
// BFS 遍历二叉树
while(que->front != NULL) {
int size = 0;
QueueNode* current = que->front;
// 计算当前层节点数
while (current != NULL) {
size++;
current = current->next;
}
// 遍历当前层节点
for (int i = 0; i < size; i++) {
struct TreeNode* node = que->front->node;
QueueNode* temp = que->front;
que->front = que->front->next;
free(temp);
// 如果是叶子节点,则返回深度
if (node->left == NULL && node->right == NULL) {
free(que);
return depth;
}
// 将左子节点加入队列
if (node->left != NULL) {
QueueNode* leftNode = (QueueNode*)malloc(sizeof(QueueNode));
leftNode->node = node->left;
leftNode->next = NULL;
if (que->front == NULL) {
que->front = leftNode;
que->rear = leftNode;
} else {
que->rear->next = leftNode;
que->rear = leftNode;
}
}
// 将右子节点加入队列
if (node->right != NULL) {
QueueNode* rightNode = (QueueNode*)malloc(sizeof(QueueNode));
rightNode->node = node->right;
rightNode->next = NULL;
if (que->front == NULL) {
que->front = rightNode;
que->rear = rightNode;
} else {
que->rear->next = rightNode;
que->rear = rightNode;
}
}
}
depth++; // 深度加一
}
free(que); // 释放队列内存
return depth;
}
java写法
class Solution {
/**
* 迭代法,层序遍历
*/
public int minDepth(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 && poll.right == null) {
// 是叶子结点,直接返回depth,因为从上往下遍历,所以该值就是最小值
return depth;
}
if (poll.left != null) {
deque.offer(poll.left);
}
if (poll.right != null) {
deque.offer(poll.right);
}
}
}
return depth;
}
}
222.完全二叉树的节点个数
给出一个完全二叉树,求出该树的节点个数。
普通二叉树
递归法
- 确定递归函数的参数和返回值:参数就是传入树的根节点,返回就返回以该节点为根节点二叉树的节点数量,所以返回值为int类型。
int getNodesNum(TreeNode* cur);
- 确定终止条件:如果为空节点的话,就返回0,表示节点数为0。
if (cur == NULL) return 0;
- 确定单层递归的逻辑:先求它的左子树的节点数量,再求右子树的节点数量,最后取总和再加一 (加1是因为算上当前中间节点)就是目前节点为根节点的节点数量。
int leftNum = getNodesNum(cur->left); // 左 int rightNum = getNodesNum(cur->right); // 右 int treeNum = leftNum + rightNum + 1; // 中 return treeNum;
完整代码
int countNodes(struct TreeNode* root) {
if (root == NULL) {
return 0; // 空树没有节点
}
// 计算左子树和右子树的节点数,然后加上根节点本身(1)
int leftCount = countNodes(root->left);
int rightCount = countNodes(root->right);
return leftCount + rightCount + 1;
}
- 时间复杂度:O(n)
- 空间复杂度:O(log n) (递归栈的深度为N,树的高度为log(N))
java写法
class Solution {
// 通用递归解法
public int countNodes(TreeNode root) {
if(root == null) {
return 0;
}
return countNodes(root.left) + countNodes(root.right) + 1;
}
}
迭代法
int countNodes(struct TreeNode* root){
//记录结点总数
int totalNum = 0;
//开辟栈空间
struct TreeNode** stack = (struct TreeNode**)malloc(sizeof(struct TreeNode*) * 100);
int stackTop = 0;
//如果root结点不为NULL,则将其入栈。若为NULL,则不会进入遍历,返回0
if(root)
stack[stackTop++] = root;
//若栈中有结点存在,则进行遍历
while(stackTop) {
//取出栈顶元素
struct TreeNode* tempNode = stack[--stackTop];
//结点总数+1
totalNum++;
//若栈顶结点有左右孩子,将它们入栈
if(tempNode->left)
stack[stackTop++] = tempNode->left;
if(tempNode->right)
stack[stackTop++] = tempNode->right;
}
return totalNum;
}
- 时间复杂度:O(n)
- 空间复杂度:O(n)
java写法
class Solution {
// 迭代法
public int countNodes(TreeNode root) {
if (root == null) return 0;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
int result = 0;
while (!queue.isEmpty()) {
int size = queue.size();
while (size -- > 0) {
TreeNode cur = queue.poll();
result++;
if (cur.left != null) queue.offer(cur.left);
if (cur.right != null) queue.offer(cur.right);
}
}
return result;
}
}
完全二叉树
在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2^(h-1) 个节点。
完全二叉树只有两种情况,情况一:就是满二叉树,情况二:最后一层叶子节点没有满。
对于情况一,可以直接用 2^树深度 - 1 来计算,注意这里根节点深度为1。
对于情况二,分别递归左孩子,和右孩子,递归到某一深度一定会有左孩子或者右孩子为满二叉树,然后依然可以按照情况1来计算。
完全二叉树(一)如图:
完全二叉树(二)如图:
可以看出如果整个树不是满二叉树,就递归其左右孩子,直到遇到满二叉树为止,用公式计算该子树(满二叉树)的节点数量。
在完全二叉树中,如果递归向左遍历的深度等于递归向右遍历的深度,那说明就是满二叉树。
在完全二叉树中,如果递归向左遍历的深度不等于递归向右遍历的深度,则说明不是满二叉树
int countNodes(struct TreeNode* root){
if(!root)
return 0;
int leftDepth = 0;
int rightDepth = 0;
struct TreeNode* rightNode = root->right;
struct TreeNode* leftNode = root->left;
//求出左子树深度
while(leftNode) {
leftNode = leftNode->left;
leftDepth++;
}
//求出右子树深度
while(rightNode) {
rightNode = rightNode->right;
rightDepth++;
}
//若左右子树深度相同,为满二叉树。结点个数为2^height-1
if(rightDepth == leftDepth) {
return (2 << leftDepth) - 1;
}
//否则返回左右子树的结点个数+1
return countNodes(root->right) + countNodes(root->left) + 1;
}
- 时间复杂度:O(log n × log n)
- 空间复杂度:O(log n)
java写法
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;
}
}