题目:二叉树的最大深度
- 给定一个二叉树
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<<sz<<endl; 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<h 0≤i<h 时,第 i 层包含 2 i 2^i 2i个节点,最底层包含的节点数最少为 1,最多为 2 h 2^h 2h。
-
当最底层包含 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;
-