root
,返回其最大深度。二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。如果我们知道了左子树和右子树的最大深度 l 和 r,那么该二叉树的最大深度即为 m a x ( l , r ) + 1 max(l,r)+1 max(l,r)+1 。而左子树和右子树的最大深度又可以以同样的方式进行计算。因此我们可以用「深度优先搜索」的方法来计算二叉树的最大深度。具体而言,在计算当前二叉树的最大深度时,可以先递归计算出其左子树和右子树的最大深度,然后在 O(1) 时间内计算出当前二叉树的最大深度。递归在访问到空节点时退出。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int maxDepth(TreeNode* root) {
if(root==nullptr)
return 0;
return max(maxDepth(root->left),maxDepth(root->right))+1;
}
};
时间复杂度:O(n),其中 n 为二叉树节点的个数。每个节点在递归中只被遍历一次。空间复杂度:O(height),其中 height 表示二叉树的高度。递归函数需要栈空间,而栈空间取决于递归的深度,因此空间复杂度等价于二叉树的高度。
广度优先搜索
用「广度优先搜索」的方法来解决这道题目,但我们需要对其进行一些修改,此时我们广度优先搜索的队列里存放的是「当前层的所有节点」。每次拓展下一层的时候,不同于广度优先搜索的每次只从队列里拿出一个节点,我们需要将队列里的所有节点都拿出来进行拓展,这样能保证每次拓展完的时候队列里存放的是当前层的所有节点,即我们是一层一层地进行拓展,最后我们用一个变量 ans 来维护拓展的次数,该二叉树的最大深度即为 ans。
class Solution {
public:
int maxDepth(TreeNode* root) {
if(root==nullptr)
return 0;
// return max(maxDepth(root->left),maxDepth(root->right))+1;
queue<TreeNode*> temp_que;
temp_que.push(root);
int ans = 0;
while(!temp_que.empty()){
int sz = temp_que.size();
// cout<
while(sz>0){
TreeNode* node=temp_que.front();
temp_que.pop();
if(node->left){
temp_que.push(node->left);
}
if(node->right){
temp_que.push(node->right);
}
sz-=1;
}
ans+=1;
}
return ans;
}
};
首先可以想到使用深度优先搜索的方法,遍历整棵树,记录最小深度。对于每一个非叶子节点,我们只需要分别计算其左右子树的最小叶子节点深度。这样就将一个大问题转化为了小问题,可以递归地解决该问题。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int minDepth(TreeNode* root) {
if(root==nullptr){
return 0;
}
if(root->left==nullptr&&root->right==nullptr){
return 1;
}
int min_depth=INT_MAX;
if(root->left!=nullptr){
min_depth=min(minDepth(root->left),min_depth);
}
if(root->right!=nullptr){
min_depth=min(minDepth(root->right),min_depth);
}
return min_depth+1;
}
};
时间复杂度:O(N),其中 N 是树的节点数。对每个节点访问一次。空间复杂度:O(H),其中 H 是树的高度。空间复杂度主要取决于递归时栈空间的开销,最坏情况下,树呈现链状,空间复杂度为 O(N)。平均情况下树的高度与节点数的对数正相关,空间复杂度为 O(logN)。
广度优先搜索
当我们找到一个叶子节点时,直接返回这个叶子节点的深度。广度优先搜索的性质保证了最先搜索到的叶子节点的深度一定最小。
class Solution {
public:
int minDepth(TreeNode* root) {
if(root==nullptr){
return 0;
}
queue<pair<TreeNode *,int>> temp_que;
temp_que.emplace(root,1);
while(!temp_que.empty()){
TreeNode *temp_node = temp_que.front().first;
int depth = temp_que.front().second;
temp_que.pop();
if(temp_node->left==nullptr&&temp_node->right==nullptr){
return depth;
}
if(temp_node->left!=nullptr){
temp_que.emplace(temp_node->left,depth+1);
}
if(temp_node->right!=nullptr){
temp_que.emplace(temp_node->right,depth+1);
}
}
return 0;
}
};
时间复杂度:O(N),其中 N 是树的节点数。对每个节点访问一次。空间复杂度:O(N),其中 N 是树的节点数。空间复杂度主要取决于队列的开销,队列中的元素个数不会超过树的节点数。
给你一棵 完全二叉树 的根节点 root
,求出该树的节点个数。完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1 ~ 2 h 1~ 2^h 1~2h 个节点。
深度优先遍历数的所有节点,不过没有使用到完全二叉树的性质。时间复杂度为 O(n),空间复杂度为 O(1)【不考虑递归调用栈】
class Solution {
public:
int countNodes(TreeNode* root) {
int count=0;
if(root==nullptr){
return 0;
}
int left=countNodes(root->left);
int right=countNodes(root->right);
return left+right+1;
}
};
确定递归函数的参数和返回值:参数就是传入树的根节点,返回就返回以该节点为根节点二叉树的节点数量,所以返回值为int类型。确定终止条件:如果为空节点的话,就返回0,表示节点数为0。确定单层递归的逻辑:先求它的左子树的节点数量,再求右子树的节点数量,最后取总和再加一 (加1是因为算上当前中间节点)就是目前节点为根节点的节点数量。
对于任意二叉树,都可以通过广度优先搜索或深度优先搜索计算节点个数,时间复杂度和空间复杂度都是 O(n),其中 n 是二叉树的节点个数。这道题规定了给出的是完全二叉树,因此可以利用完全二叉树的特性计算节点个数。规定根节点位于第 0 层,完全二叉树的最大层数为 h。根据完全二叉树的特性可知,完全二叉树的最左边的节点一定位于最底层,因此从根节点出发,每次访问左子节点,直到遇到叶子节点,该叶子节点即为完全二叉树的最左边的节点,经过的路径长度即为最大层数 h。当 0 ≤ i < h 0≤i
当最底层包含 1 个节点时,完全二叉树的节点个数是 ∑ i = 0 h − 1 2 i + 1 = 2 h \sum_{i=0}^{h-1}2^i+1=2^h ∑i=0h−12i+1=2h;
当最底层包含 2 h 2^h 2h 个节点时,完全二叉树的节点个数是 ∑ i = 0 h 2 i = 2 h + 1 − 1 \sum_{i=0}^{h}2^i=2^{h+1}-1 ∑i=0h2i=2h+1−1
因此对于最大层数为 h 的完全二叉树,节点个数一定在 [ 2 h , 2 h + 1 − 1 ] [2^h,2^{h+1}-1] [2h,2h+1−1] 的范围内,可以在该范围内通过二分查找的方式得到完全二叉树的节点个数。具体做法是,根据节点个数范围的上下界得到当前需要判断的节点个数 k,如果第 k 个节点存在,则节点个数一定大于或等于 k,如果第 k 个节点不存在,则节点个数一定小于 k,由此可以将查找的范围缩小一半,直到得到节点个数。
如何判断第 k 个节点是否存在呢?如果第 k 个节点位于第 h 层,则 k 的二进制表示包含 h+1 位,其中最高位是 1,其余各位从高到低表示从根节点到第 k 个节点的路径,0 表示移动到左子节点,1 表示移动到右子节点。通过位运算得到第 k 个节点对应的路径,判断该路径对应的节点是否存在,即可判断第 k 个节点是否存在。222. 完全二叉树的节点个数 - 力扣(LeetCode)
class Solution {
public:
bool exists(TreeNode *root,int high,int k){
int bits=1<<(high-1);
TreeNode* node =root;
while(node!=nullptr&&bits>0){
if(!(bits&k)){
node=node->left;
}else{
node=node->right;
}
bits>>=1;
}
return node!=nullptr;
}
int countNodes(TreeNode* root) {
if(root==nullptr){
return 0;
}
// int left=countNodes(root->left);
// int right=countNodes(root->right);
// return left+right+1;
int high=0;
TreeNode *node = root;
while(node->left!=nullptr){
high++;
node = node->left;
}
int min_count=1<<high,max_count=(1<<(high+1))-1;
while(min_count<max_count){
int mid=(max_count-min_count+1)/2+min_count;
if(exists(root,high,mid)){
min_count=mid;
}else{
max_count=mid-1;
}
}
return min_count;
}
};
时间复杂度: O ( log 2 n ) O(\log^2 n) O(log2n),其中 n 是完全二叉树的节点数。 首先需要 O(h) 的时间得到完全二叉树的最大层数,其中 h 是完全二叉树的最大层数。 使用二分查找确定节点个数时,需要查找的次数为 O ( log 2 h ) = O ( h ) O(\log 2^h)=O(h) O(log2h)=O(h),每次查找需要遍历从根节点开始的一条长度为 h 的路径,需要 O(h) 的时间,因此二分查找的总时间复杂度是 O ( h 2 ) O(h^2) O(h2)。 因此总时间复杂度是 O ( h 2 O(h^2 O(h2。由于完全二叉树满足 2 h ≤ n < 2 h + 1 2^h \le n < 2^{h+1} 2h≤n<2h+1,因此有 O ( h ) = O ( log n ) O(h)=O(\log n) O(h)=O(logn), O ( h 2 ) = O ( log 2 n ) O(h^2)=O(\log^2 n) O(h2)=O(log2n)。空间复杂度:O(1)。只需要维护有限的额外空间。
解法3:判断其子树是不是满二叉树,如果是则利用公式计算这个子树(满二叉树)的节点数量,如果不是则继续递归,那么 在递归三部曲中,第二部:终止条件的写法应该是这样的:
if (root == nullptr) return 0;
// 开始根据左深度和右深度是否相同来判断该子树是不是满二叉树
TreeNode* left = root->left;
TreeNode* right = root->right;
int leftDepth = 0, rightDepth = 0; // 这里初始为0是有目的的,为了下面求指数方便
while (left) { // 求左子树深度
left = left->left;
leftDepth++;
}
while (right) { // 求右子树深度
right = right->right;
rightDepth++;
}
if (leftDepth == rightDepth) {
return (2 << leftDepth) - 1; // 注意(2<<1) 相当于2^2,返回满足满二叉树的子树节点数量
}
递归三部曲,第三部,单层递归的逻辑:(可以看出使用后序遍历)
int leftTreeNum = countNodes(root->left); // 左
int rightTreeNum = countNodes(root->right); // 右
int result = leftTreeNum + rightTreeNum + 1; // 中
return result;