515m. 在每个树行中找最大值
方法一:BFS
用时:7m29s
思路
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( n ) O(n) O(n)
C++代码
/**
* 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:
vector<int> largestValues(TreeNode* root) {
queue<TreeNode*> que;
vector<int> res;
if (root == nullptr) return res;
que.push(root);
while (!que.empty()) {
int maxNum = INT_MIN;
int size = que.size();
for (int i = 0; i < size; ++i) {
TreeNode* node = que.front();
que.pop();
if (node->val > maxNum) maxNum = node->val;
if (node->left) que.push(node->left);
if (node->right) que.push(node->right);
}
res.push_back(maxNum);
}
return res;
}
};
看完讲解的思考
无。
代码实现遇到的问题
无。
116m. 填充每个节点的下一个右侧节点指针
方法一:
用时:8m48s
思路
BFS。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( n ) O(n) O(n)。
C++代码
/*
// Definition for a Node.
class Node {
public:
int val;
Node* left;
Node* right;
Node* next;
Node() : val(0), left(NULL), right(NULL), next(NULL) {}
Node(int _val) : val(_val), left(NULL), right(NULL), next(NULL) {}
Node(int _val, Node* _left, Node* _right, Node* _next)
: val(_val), left(_left), right(_right), next(_next) {}
};
*/
class Solution {
public:
Node* connect(Node* root) {
queue<Node*> que;
if (root == NULL) return root;
que.push(root);
while (!que.empty()) {
int size = que.size();
for (int i = 0; i < size; ++i) {
Node* node = que.front();
que.pop();
if (i < size - 1) node->next = que.front();
if (node->left) que.push(node->left);
if (node->right) que.push(node->right);
}
}
return root;
}
};
方法二:迭代法
思路
从上往下遍历,利用父节点一定是通过next相连这一特性。
-
记录当前层最左边的节点
leftMost
,用于进入下一层。 -
利用next指针,从左向右遍历当前层的节点,将下一层节点从左向右连起来:
node->left->next = node->right; // 左节点指向右节点 node->right->next = node->next->left; // 右节点指向父节点的next节点的左节点
-
遍历完当前层后,利用leftMost进入下一层:
leftMost = leftMost->left;
从第一层开始,将第二层所有元素通过next相连,进入第二层后,因为第二层已经连接好了,所以就可以利用第二层来连接第三层,一直迭代到最后一层。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( 1 ) O(1) O(1)。
C++代码
class Solution {
public:
Node* connect(Node* root) {
if (root == NULL) return root;
Node* leftMost = root;
while (leftMost->left != NULL) {
Node* node = leftMost;
while (node != NULL) {
node->left->next = node->right;
if (node->next != NULL) node->right->next = node->next->left;
node = node->next;
}
leftMost = leftMost->left;
}
return root;
}
};
看完讲解的思考
方法二好巧妙。
代码实现遇到的问题
无。
104e. 二叉树的最大深度
方法一:递归法
用时:5m58s
思路
如果是空节点则最大深度为0,否则返回左子节点的最大深度和右子节点的最大深度之间的最大值加1(加上当前节点)。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度:因递归是用栈实现的,所以空间复杂度取决于递归的最大深度,即二叉树的最大深度,故空间复杂度为 O ( d ) O(d) O(d),d是二叉树的最大深度。
C++代码
class Solution {
public:
int maxDepth(TreeNode* root) {
return root == nullptr ? 0 : max(maxDepth(root->left), maxDepth(root->right)) + 1;
}
};
方法二:BFS
思路
懒得敲了,这两天二叉树的BFS敲了N次了都。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( n ) O(n) O(n)。
看完讲解的思考
原来苯人也能写出这么简洁的代码^^
代码实现遇到的问题
无。
559e. N 叉树的最大深度
方法一:递归法
用时:3m6s
思路
同上一题。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( d ) O(d) O(d),d是N叉树的最大深度。
C++代码
/*
// Definition for a Node.
class Node {
public:
int val;
vector<Node*> children;
Node() {}
Node(int _val) {
val = _val;
}
Node(int _val, vector<Node*> _children) {
val = _val;
children = _children;
}
};
*/
class Solution {
public:
int maxDepth(Node* root) {
if (root == nullptr) return 0;
int maxD = 0;
for (Node*& child : root->children) maxD = max(maxD, maxDepth(child));
return maxD + 1;
}
};
方法二:BFS
思路
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( n ) O(n) O(n)。
看完讲解的思考
无。
代码实现遇到的问题
无。
111e. 二叉树的最小深度
方法一:递归法
用时:6m4s
思路
若节点为空,则最小深度为0;
若节点没有左节点,则最小深度为右节点的最小深度加1;
若节点没有右节点,则最小深度为左节点的最小深度加1;
若节点有左右节点,则最小深度为左节点的最小深度和右节点的最小深度之间的最小值加1。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( d ) O(d) O(d),d为二叉树的最大深度。
C++代码
class Solution {
public:
int minDepth(TreeNode* root) {
if (root == nullptr) return 0;
else if (root->left == nullptr) return minDepth(root->right) + 1;
else if (root->right == nullptr) return minDepth(root->left) + 1;
else return min(minDepth(root->left), minDepth(root->right)) + 1;
}
};
方法二:BFS
思路
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( n ) O(n) O(n)。
看完讲解的思考
无。
代码实现遇到的问题
无。
222m. 完全二叉树的节点个数
方法一:BFS
用时:9m5s
思路
BFS遍历所有节点,统计节点个数。
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( n ) O(n) O(n)。
C++代码
class Solution {
public:
int countNodes(TreeNode* root) {
queue<TreeNode*> que;
int num = 0;
if (root == nullptr) return num;
que.push(root);
while (!que.empty()) {
int size = que.size();
num += size;
for (int i = 0; i < size; ++i) {
TreeNode* node = que.front();
if (node->left) que.push(node->left);
if (node->right) que.push(node->right);
que.pop();
}
}
return num;
}
};
方法二:递归遍历
思路
- 时间复杂度: O ( n ) O(n) O(n)。
- 空间复杂度: O ( log n ) O(\log{n}) O(logn)。空间复杂度由递归的最大深度决定,即二叉树的最大深度,由于本题的二叉树是完全二叉树, n n n为完全二叉树的节点个数,则完全二叉树的最大深度与 log n \log{n} logn同一数量级,所以空间复杂度为 O ( log n ) O(\log{n}) O(logn)。
C++代码
class Solution {
public:
int countNodes(TreeNode* root) {
return root == nullptr ? 0 : countNodes(root->left) + countNodes(root->right) + 1;
}
};
方法三:递归+完美二叉树
思路
方法一、二本质上都是遍历所有节点来统计节点数,由于本题的二叉树是完全二叉树,所以可以利用完全二叉树的性质来优化算法。
- 如果一棵完全二叉树,向左遍历的最大深度等于向右遍历的最大深度,则它是一棵完美二叉树。
- 对于一棵完美二叉树,若它的深度为d,则它的节点数量为 2 d − 1 2^d-1 2d−1。
利用以上两点性质,我们可以用优于
O
(
n
)
O(n)
O(n)的时间复杂度完成。
首先利用性质1判断以当前节点作为根节点的二叉树是不是完美二叉树,若是的话,则直接能够计算出当前二叉树的节点数。
如当前的二叉树不是完美二叉树,则递归判断当前节点的左右节点是否是完美二叉树,当前的二叉树的节点数为:左子树节点数+右子树节点数+1。
- 时间复杂度: O ( log 2 n ) O(\log^2n) O(log2n)。在每一次递归之中,时间复杂度是 O ( d i ) O(d_i) O(di),其中 d i d_i di是第 i i i次递归时二叉树的深度, d = d 1 d=d_1 d=d1是整棵树的深度,那么 d i = d − i + 1 , i = 1 , 2 , . . . d_i=d-i+1, i=1,2,... di=d−i+1,i=1,2,...。假设最坏情况,每次递归的子树都不是完美二叉树,故递归要一直进行到二叉树的最底层,那么时间复杂度就为 O ( d 1 + d 2 + . . . + d d ) = O ( d + ( d − 1 ) + . . . + 1 ) = O ( d 2 ) O(d_1+d_2+...+d_{d})=O(d+(d-1)+...+1)=O(d^2) O(d1+d2+...+dd)=O(d+(d−1)+...+1)=O(d2),由于本题中的树是完全二叉树,所以 O ( d ) = O ( log n ) O(d)=O(\log n) O(d)=O(logn),故时间复杂度为 O ( d 2 ) = O ( log 2 n ) O(d^2)=O(\log^2n) O(d2)=O(log2n)。
- 空间复杂度: O ( log n ) O(\log n) O(logn)。完全二叉树的深度。
C++代码
class Solution {
public:
int countNodes(TreeNode* root) {
if (root == nullptr) return 0;
int leftDepth = 1;
int rightDepth = 1;
TreeNode* leftNode = root;
TreeNode* rightNode = root;
while (leftNode->left) {
++leftDepth;
leftNode = leftNode->left;
}
while (rightNode->right) {
++rightDepth;
rightNode = rightNode->right;
}
if (leftDepth == rightDepth) return pow(2, leftDepth) - 1;
else return countNodes(root->left) + countNodes(root->right) + 1;
}
};
方法四:二分查找+位运算
思路
若一棵完全二叉树的深度为 d d d,给每个节点从1开始编号,则它的节点数一定位于 [ 2 d − 1 , 2 d − 1 ] [2^{d-1},2^d-1] [2d−1,2d−1]区间。对于 [ 2 d − 1 , 2 d − 1 ] [2^{d-1},2^d-1] [2d−1,2d−1]区间,可以使用二分查找来获取最后一个节点的位置编号,即节点数。
二分查找:
左边界为
2
d
−
1
2^{d-1}
2d−1,右边界为
2
d
−
1
2^d-1
2d−1,每次判断中间位置的节点是否存在,然后更新左右边界的位置,直至两边界重合,找到最后一个节点的位置,位置的编号即为二叉树节点的个数。
判断某个节点是否存在:
对于一棵完全二叉树,每个节点可以用二进制编码。根节点从1开始,如图所示,向左走新增的位就是0,向右走新增的位就是1。
根据以上性质,要判断第k个节点是否存在,将k转化成二进制编码,然后根据编码可以从根节点出发到达第k个节点,若第k个节点为空,则不存在。例如:对于节点10,二进制编码为1010,第一位1是根节点,第二位0表示向左走,第三位1表示向右走,第四位0表示向左走,即可到达节点10。
使用位运算实现:
-
获取二进制编码第n个位上的数字可以使用位运算实现。例如:对于四位编码1010,想获取第3位的数字,就将该编码和0010进行与运算,相与的结果为0则说明第3位的数字为0,相与的结果不为0,则说明第3位的数字为1,1010 & 0010 = 0010,结果不为0,说明1010的第3位为1。
-
设置一个变量
check
,用于获取编码某一位的数字,check
初始化为1 << (depth - 2)
,例如:若二叉树有3层,check初始化为010(1左移3-1=1位得到10,即010),因为第一位默认是1(根节点),所以从第2位开始获取(check第2位为1);同理,若二叉树有4层,初始化为0100。 -
获取到的编码的对应位数字后,若数字为0则节点向左走,若为1则节点向右走。
-
每次循环将check不断右移,0100->0010->0001,逐个位遍历编码。
-
时间复杂度: O ( log 2 n ) O(\log^2n) O(log2n), n n n为树的节点数。二分查找时间复杂度为 O ( log n ) O(\log n) O(logn),判断节点存不存在的时间复杂度为 O ( log n ) O(\log n) O(logn)(树的深度)。
-
空间复杂度: O ( 1 ) O(1) O(1)。
C++代码
class Solution {
public:
int countNodes(TreeNode* root) {
if (root == nullptr) return 0;
if (root->left == nullptr) return 1;
// 二叉树的最大深度
int depth = 0;
for(TreeNode* node = root; node; node = node->left) ++depth;
// 二分查找
int left = pow(2, depth - 1);
int right = pow(2, depth) - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (exists(root, depth, mid)) left = mid + 1;
else right = mid - 1;
}
return right;
}
bool exists(TreeNode* root, int depth, int k) {
/*
root:二叉树的根节点
depth:二叉树的最大深度
k:检查编号为k的节点是否存在
*/
cout << k << endl;
int check = 1 << (depth - 2); // 01000...
TreeNode* node = root;
while (check && node) {
if (check & k) node = node->right; // 当前编码为1,则向右走
else node = node->left; // 当前编码为0,则向左走
check >>= 1; // 将1不断右移,逐位遍历
}
return node != nullptr;
}
};
看完讲解的思考
本以为方法三已经够妙了,方法四…
代码实现遇到的问题
无。
110e. 平衡二叉树
方法一:递归法
思路
对于一个根节点,只有满足以下两个条件之一,该根节点对应的二叉树才是平衡二叉树:
- 根节点是空节点。
- 左子树是平衡二叉树,右子树也是平衡二叉树,左右子树的高度差小于等于1。
对于左右子树,可以递归判断左右子树是否是平衡二叉树。获取左右子树的高度如第104e题。
- 时间复杂度: O ( n log n ) O(n \log n) O(nlogn)。考虑最坏情况,当二叉树是完美二叉树,此时递归的时间复杂度是 O ( n ) O(n) O(n),每次递归中需要计算子树的高度,时间复杂度为 O ( log n ) O(\log n) O(logn),故总体的时间复杂度为 O ( n log n ) O(n \log n) O(nlogn)。
- 空间复杂度: O ( n ) O(n) O(n)。空间复杂度取决于递归的深度,最坏情况下,二叉树呈链状,递归的深度为n,故空间复杂度为 O ( n ) O(n) O(n)。
C++代码
class Solution {
public:
bool isBalanced(TreeNode* root) {
return root == nullptr || (abs(height(root->left) - height(root->right)) <= 1 && isBalanced(root->left) && isBalanced(root->right)) ? true : false;
}
int height(TreeNode* node) {
return node == nullptr ? 0 : max(height(node->left), height(node->right)) + 1;
}
};
方法二:递归法优化
用时:
思路
方法一存在可以优化的地方,在方法一中每个节点都要多次调用height函数计算高度,在递归当中嵌套了递归,可以将计算高度和判断是否是平衡二叉树两个递归用一个递归来实现。
当二叉树是一课平衡二叉树,我们就返回它的高度,当它不是平衡二叉树时,我们就返回-1。对于一个节点,先计算其左子树的高度,若左子树高度为-1,则表示左子树不是平衡二叉树,那么就没有必要计算右子树的了,直接返回-1;计算右子树高度时同理。
所以最后我们只需判断该二叉树的高度是否为-1,不是-1就说明它是一棵平衡二叉树。
- 时间复杂度: O ( n ) O(n) O(n)。只有单个递归,故时间复杂度为 O ( n ) O(n) O(n)。
- 空间复杂度: O ( n ) O(n) O(n)。
C++代码
class Solution {
public:
bool isBalanced(TreeNode* root) {
return height(root) == -1 ? false : true;
}
int height(TreeNode* node) {
if (node == nullptr) return 0;
int leftH = height(node->left);
if (leftH == -1) return -1;
int rightH = height(node->right);
if (rightH == -1) return -1;
return abs(leftH - rightH) > 1 ? -1 : max(leftH, rightH) + 1;
}
};
看完讲解的思考
无。
代码实现遇到的问题
无。
最后的碎碎念
二叉树越刷越熟了。