一、昨日回顾与补充
今天看了Day16讲解的视频,对于求二叉树最大深度、最小深度以及求完全二叉树的节点个数有了新的理解,总结如下:
1.深度和高度的区别(之前就看看定义忽略了)
二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数。
二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数。
在递归遍历的过程中,按照正常逻辑来说应该是前序遍历(中左右)自上而下求深度,但是这样写的代码比较麻烦,因此大多采用后序(左右中)自下而上的写法将求深度间接转化为求高度的过程。具体如下:
求二叉树的最大深度即求根节点到最远叶子节点的高度。
根节点的高度就是二叉树的最大深度,所以本题中可以通过后序求的根节点高度来求的二叉树最大深度。
求二叉树的最小深度即求根节点到最近左右子树都为空的叶子节点的高度。
使用后序遍历,其实求的是根节点到叶子节点的最小距离,就是求高度的过程,不过这个最小距离 也同样是最小深度。
2. LeetCode104 二叉树的最大深度 LeetCode111 二叉树的最小深度
// 最大深度——简化版
class Solution {
public:
int maxDepth(TreeNode* root) {
if (root == NULL) return 0;
return 1 + max(maxDepth(root->left), maxDepth(root->right));
}
};
// 具体后序遍历实现逻辑
class Solution {
public:
// 通过后序遍历求根节点到叶子结点的最大高度 = 二叉树的最大深度
int getHeight(TreeNode* node) {
if (node == NULL) return 0;
int leftHeight = getHeight(node->left); // 左
int rightHeight = getHeight(node->right); // 右
int res = 1 + max(leftHeight, rightHeight);// 中
return res;
}
int maxDepth(TreeNode* root) {
return getHeight(root);
}
};
// 最小深度——简化版
class Solution {
public:
int minDepth(TreeNode* root) {
if (root == NULL) return 0;
if (root->left == NULL && root->right != NULL) {
return 1 + minDepth(root->right);
}
if (root->left != NULL && root->right == NULL) {
return 1 + minDepth(root->left);
}
return 1 + min(minDepth(root->left), minDepth(root->right));
}
};
// 具体后序遍历实现逻辑
class Solution {
public:
int getHeight(TreeNode* node) {
if (node == NULL) return 0;
int leftHeight = getHeight(node->left); // 左
int rightHeight = getHeight(node->right); // 右
// 中
if (node->left == NULL && node->right != NULL) {
return 1 + rightHeight;
}
if (node->left != NULL && node->right == NULL) {
return 1 + leftHeight;
}
return 1 + min(leftHeight, rightHeight);
}
int minDepth(TreeNode* root) {
return getHeight(root);
}
};
3. 完全二叉树的解法
方法一,当做普通二叉树(迭代法和递归法)——迭代法采用队列的方式实现,递归法采用后序遍历的方式,简化版真的很简单。
方法二,充分利用完全二叉树的特点来做。在递归的思路中,对某节点将定义两个指针left和right,分别指向左右孩子,利用while循环遍历左子树的节点深度和右子树的深度。如果得到的遍历结果是相等的,那么证明以当前节点为根节点的子树是一颗满二叉树(它是完全二叉树的子集),因此可以利用满二叉树的公式求节点个数(即2^深度-1),具体代码实现可以采用位运算,这样在树比较大时能节省很多时间。
// 方法一:当做普通二叉树来求节点个数的简化版代码
class Solution {
public:
int countNodes(TreeNode* root) {
if (root == NULL) return 0;
return 1 + countNodes(root->left) + countNodes(root->right);
}
};
// 具体逻辑实现(后序遍历)
class Solution {
public:
int getNodesSum(TreeNode* cur) {
if (cur == NULL) return 0;
int leftNum = getNodesSum(cur->left); // 左
int rightNum = getNodesSum(cur->right); // 右
int treeNum = leftNum + rightNum + 1; // 中
return treeNum;
}
int countNodes(TreeNode* root) {
return getNodesSum(root);
}
};
// 当做普通二叉树的迭代法实现代码——即层序遍历的模板
class Solution {
public:
int countNodes(TreeNode* root) {
queue<TreeNode*> que;
int result = 0;
if (root != NULL) que.push(root);
while (!que.empty()) {
int size = que.size();
for (int i = 0; i < size; i++) {
TreeNode* node = que.front();
que.pop();
result++;
if (node->left) que.push(node->left);
if (node->right) que.push(node->right);
}
}
return result;
}
};
// 方法二:利用完全二叉树的特征,递归版的简化版代码
class Solution {
public:
int countNodes(TreeNode* root) {
if (root == NULL) return 0;
TreeNode* left = root->left;
TreeNode* right = root->right;
int leftNums = 0, rightNums = 0;
while (left) {
left = left->left;
leftNums ++;
}
while (right) {
right = right->right;
rightNums ++;
}
if (leftNums == rightNums) {
return (2 << leftNums) - 1;
}
return countNodes(root->left) + countNodes(root->right) + 1;
}
};
// 具体逻辑实现
class Solution {
public:
int getNums(TreeNode* node) {
if (node == NULL) return 0;
TreeNode* left = node->left;
TreeNode* right = node->right;
int leftNums = 0;
int rightNums = 0;
// 一直向左遍历左子树
while (left) {
left = left->left;
leftNums++;
}
// 一直向右遍历右子树
while (right) {
right = right->right;
}
if (leftNums == rightNums) {
// 左移 位运算
return (2 << leftNums) - 1;
}
leftNums = getNums(node->left);
rightNums = getNums(node->right);
return 1 + leftNums + rightNums;
}
int countNodes(TreeNode* root) {
return getNums(root);
}
};
二、参考资料
平衡二叉树 (优先掌握递归)
题目链接/文章讲解/视频讲解:https://programmercarl.com/0110.%E5%B9%B3%E8%A1%A1%E4%BA%8C%E5%8F%89%E6%A0%91.html
二叉树的所有路径 (优先掌握递归)
题目链接/文章讲解/视频讲解:https://programmercarl.com/0257.%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E6%89%80%E6%9C%89%E8%B7%AF%E5%BE%84.html
左叶子之和 (优先掌握递归)
题目链接/文章讲解/视频讲解:https://programmercarl.com/0404.%E5%B7%A6%E5%8F%B6%E5%AD%90%E4%B9%8B%E5%92%8C.html
三、LeetCode110.平衡二叉树
https://leetcode.cn/problems/balanced-binary-tree/description/
示例1:
示例2:
给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:
一个二叉树 每个节点 的左右两个子树的高度差的绝对值不超过 1 。
示例 1:(如上图)
输入:root = [3,9,20,null,null,15,7] 输出:true
示例 2:(如上图)
输入:root = [1,2,2,3,3,null,null,4,4] 输出:false
示例 3:
输入:root = [] 输出:true
提示:
树中的节点数在范围 [0, 5000] 内
-10^4 <= Node.val <= 10^4
/**
* 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 getHeight(TreeNode* node) {
if (node == NULL) return 0;
int leftHeight = getHeight(node->left); // 左
int rightHeight = getHeight(node->right); // 右
if (leftHeight == -1) return -1;
if (rightHeight == -1) return -1;
int res = -1; // 中
if (abs(leftHeight - rightHeight) > 1) {
res = -1;
} else {
res = 1 + max(leftHeight, rightHeight); // 父节点 + 其子树的高度
}
return res;
}
bool isBalanced(TreeNode* root) {
int res = getHeight(root);
if (res == -1) return false;
return true;
}
};
// 不断简化一
class Solution {
public:
// int isbal = true;
// 后序遍历(左右中)自下而上求高度,不适合采用前序,如果先判断中间节点,会出现由于其子树的高度未知无法进一步得到当前的子树是否是平衡二叉树
int getHeight(TreeNode* node) {
if (node == NULL) return 0;
int leftHeight = getHeight(node->left); // 左
int rightHeight = getHeight(node->right); // 右
// 剪枝 如果某节点的子树已经不是平衡二叉树,那么整棵树就不是平衡二叉树,不用再继续求高度差
if (leftHeight == -1 || rightHeight == -1) return -1; // 中
if (abs(leftHeight - rightHeight) > 1) {
return -1;
} else {
return 1 + max(leftHeight, rightHeight); // 父节点 + 其子树的高度
}
}
bool isBalanced(TreeNode* root) {
if (getHeight(root) == -1) return false;
return true;
}
};
// 不断简化二
class Solution {
public:
int getHeight(TreeNode* node) {
if (node == NULL) return 0;
int leftHeight = getHeight(node->left); // 左
int rightHeight = getHeight(node->right); // 右
if (leftHeight == -1 || rightHeight == -1) return -1; // 中
if (abs(leftHeight - rightHeight) > 1) {
return -1;
} else {
return 1 + max(leftHeight, rightHeight); // 父节点 + 其子树的高度
}
}
bool isBalanced(TreeNode* root) {
if (getHeight(root) == -1) return false;
return true;
}
};
// 不断简化三
class Solution {
public:
// -1 表示已经不是平衡二叉树了,否则返回值是以该节点为根节点树的高度
int getHeight(TreeNode* node) {
if (node == NULL) return 0;
int leftHeight = getHeight(node->left); // 左
if (leftHeight == -1) return -1;
int rightHeight = getHeight(node->right); // 右
if (rightHeight == -1) return -1;
return abs(leftHeight - rightHeight) > 1 ? -1 : 1 + max(leftHeight, rightHeight);
}
bool isBalanced(TreeNode* root) {
return getHeight(root) == -1 ? false : true;
}
};
迭代法(不是很熟练)
/**
* 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 {
// 迭代法:请注意区别,在求最大深度的时候,因为求根节点的高度即为树的最大深度,因此可以通过后序遍历来求高度,进而等价于求最大深度。
// 在求最大深度的题目中,可以使用层序遍历来求深度,但是就不能直接用层序遍历来求高度了,这就体现出求高度和求深度的不同。(前序遍历,中左右的顺序,可以通过队列来模拟广搜,也是层序遍历的模板)
// 本题的迭代方式可以先定义一个函数,专门用来求高度。这个函数通过栈模拟的后序遍历找每一个节点的高度(其实是通过求传入节点为根节点的最大深度来求的高度)
private:
// cur节点的最大深度,就是cur的高度
int getDepth(TreeNode* cur) {
stack<TreeNode*> st;
if (cur != NULL) st.push(cur);
int depth = 0; // 记录深度
int result = 0;
while (!st.empty()) {
TreeNode* node = st.top();
if (node != NULL) {
st.pop();
st.push(node); // 中
st.push(NULL);
depth++;
if (node->right) st.push(node->right); // 右
if (node->left) st.push(node->left); // 左
} else {
st.pop();
node = st.top();
st.pop();
depth--;
}
result = result > depth ? result : depth;
}
return result;
}
public:
bool isBalanced(TreeNode* root) {
stack<TreeNode*> st;
if (root == NULL) return true;
st.push(root);
while (!st.empty()) {
TreeNode* node = st.top(); // 中
st.pop();
// 判断左右孩子高度是否符合
if (abs(getDepth(node->left) - getDepth(node->right)) > 1) {
return false;
}
if (node->right) st.push(node->right); // 右(空节点不入栈)
if (node->left) st.push(node->left); // 左(空节点不入栈)
}
return true;
}
};
四、LeetCode257. 二叉树的所有路径
https://leetcode.cn/problems/binary-tree-paths/
给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。
叶子节点 是指没有子节点的节点。
示例 1:
输入:root = [1,2,3,null,5] 输出:["1->2->5","1->3"]
示例 2:
输入:root = [1] 输出:["1"]
提示:
树中节点的数目在范围 [1, 100] 内
-100 <= Node.val <= 100
/**
* 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 {
private:
// version 1
void traversal (TreeNode* node, vector<int>& path, vector<string>& res) {
// 递归终止条件 到达叶子节点
if (node->left == NULL && node->right == NULL) {
path.push_back(node->val); // 将最后一个节点加入路径中
string sPath;
for (int i = 0; i < path.size() - 1; i++) {
sPath += to_string(path[i]);
sPath += "->";
}
sPath += to_string(path[path.size() - 1]);
res.push_back(sPath);
return;
}
// 中,当前节点
path.push_back(node->val);
// 左
if (node->left) {
traversal(node->left, path, res);
path.pop_back(); // 回溯
}
// 右
if (node->right) {
traversal(node->right, path, res);
path.pop_back(); // 回溯
}
}
public:
vector<string> binaryTreePaths(TreeNode* root) {
vector<string> res;
vector<int> path;
if (root == NULL) return res;
traversal(root, path, res);
return res;
}
};
/**
* 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 {
// version 2 版本一的精简版,省略了体现回溯的逻辑
private:
void traversal(TreeNode* node, string path, vector<string>& res) {
if (node->left == NULL && node->right == NULL) {
path += to_string(node->val);
res.push_back(path);
return ;
}
// 中
path += to_string(node->val);
if (node->left) traversal(node->left, path + "->", res); // 左
if (node->right) traversal(node->right, path + "->", res); // 右
}
public:
vector<string> binaryTreePaths(TreeNode* root) {
string path;
vector<string> res;
if (root == NULL) return res;
traversal(root, path, res);
return res;
}
};
迭代法
/**
* 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 {
// version3 迭代法 除了模拟递归需要一个栈,同时还需要一个栈来存放对应的遍历路径
public:
vector<string> binaryTreePaths(TreeNode* root) {
stack<TreeNode*> treeSt; // 保存树的遍历节点
stack<string> pathSt; // 保存遍历路径的节点
vector<string> res; // 保存最终路径集合
if (root == NULL) return res;
treeSt.push(root);
pathSt.push(to_string(root->val));
while (!treeSt.empty()) {
TreeNode* node = treeSt.top(); treeSt.pop(); // 取出节点 中
string path = pathSt.top(); pathSt.pop(); // 取出该节点对应的路径
// 遇到叶子节点
if (node->left == NULL && node->right == NULL) {
res.push_back(path);
}
// 右
if (node->right) {
treeSt.push(node->right);
pathSt.push(path + "->" + to_string(node->right->val));
}
// 左
if (node->left) {
treeSt.push(node->left);
pathSt.push(path + "->" + to_string(node->left->val));
}
}
return res;
}
};
五、LeetCode404.左叶子之和
https://leetcode.cn/problems/sum-of-left-leaves/
示例1:
给定二叉树的根节点 root ,返回所有左叶子之和。
示例 1:(如上图)
输入: root = [3,9,20,null,null,15,7] 输出: 24 解释: 在这个二叉树中,有两个左叶子,分别是 9 和 15,所以返回 24
示例 2:
输入: root = [1] 输出: 0
提示:
节点数在 [1, 1000] 范围内
-1000 <= Node.val <= 1000
/**
* 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 sumOfLeftLeaves(TreeNode* root) {
if (root == NULL) return 0;
if (root->left == NULL && root->right == NULL) return 0; // 叶子节点(剪枝,减少递归)
int leftNum = sumOfLeftLeaves(root->left); // 左
// 左子树就是一个左叶子的情况
if (root->left && root->left->left == NULL && root->left->right == NULL ) {
leftNum = root->left->val;
}
int rightNum = sumOfLeftLeaves(root->right); // 右
int sum = leftNum + rightNum; // 中
return sum;
}
};
class Solution {
public:
// 递归精简版
int sumOfLeftLeaves(TreeNode* root) {
if (root == NULL) return 0;
if (!root->left && !root->right) return 0;
int leftNum = 0;
if (root->left && !root->left->left && !root->left->right) {
leftNum = root->left->val;
}
return leftNum + sumOfLeftLeaves(root->left) + sumOfLeftLeaves(root->right);
}
};
迭代法:
class Solution {
// 迭代法——使用前中后序都是可以的,只要把左叶子节点统计出来,就可以了。以前序遍历为例
public:
int sumOfLeftLeaves(TreeNode* root) {
stack<TreeNode*> st;
if (root == NULL) return 0;
st.push(root);
int res = 0;
while (!st.empty()) {
TreeNode* node = st.top();
st.pop();
if (node->left && !node->left->left && !node->left->right) {
res += node->left->val;
}
if (node->left) st.push(node->left);
if (node->right) st.push(node->right);
}
return res;
}
};
总结:
看完视频对于二叉树求最大/最小深度、高度题目有了新的理解,这应该是今天的重要收获!
尽管简化版代码真的很简单,但是背代码不是最终的目标,刷题的意义在于理解真正的实现逻辑和思路,能够写出易懂的复杂代码,等到后面熟悉之后,自然而然能化繁为简,简化代码的行数。
杜绝浮躁,踏踏实实刷题,这样的状态坚持住嗷,也希望通过博客记录的方式不断督促自己呢
以前非常不敢刷树的题目,感觉思路理解,一写就废。但是这几天的集中训练下来,收获多多~
进度是越来越跟不上啦,这周末看来又要加班嗷
刷题加油鸭~