给定一个二叉树,判断它是否是高度平衡的二叉树。本题中,一课高度平衡的二叉树定义为:一个二叉树每个节点的左右两个子树的高度差的绝对值不超过1。在开始题目之前,需要明确二叉树的深度和高度,是两种不同的概念。二叉树的深度,指根节点到当前节点的最长路径;二叉树的高度,指当前节点到叶子节点的最长路径。所以,在104.二叉树的最大深度中,我们通过求根节点的高度,进而得到二叉树的最大深度,因为根节点的高度就是这颗二叉树的最大深度。
求深度,是自上而下;求高度,是自下而上。因此,本题不能使用层序遍历,因为层序遍历是自上而下。根据平衡二叉树的定义,所有子树都是平衡二叉树,使用后序遍历,求左右子树的高度,并判断是否为平衡二叉树。为什么使用后序遍历?需要先获得左右孩子节点的信息,才能判断当前节点的深度,所以使用左右根的顺序,收集孩子节点信息。
class Solution { // 后序遍历,迭代法实现
public:
int getHeight(TreeNode* node) { // 1. 明确形参列表,根节点
if (node == nullptr) return 0; // 2. 明确终止条件,在当前节点为nullptr时,终止递归
// 3. 明确递归的基本逻辑
int leftHeight = getHeight(node->left); // 向左子树递归,求深度
if (leftHeight == -1) return -1; // 判断以当前节点为根节点的二叉树是否为平衡二叉树
int rightHeight = getHeight(node->right); // 向右子树递归,求深度
if (rightHeight == -1) return -1; // 判断以当前节点为根节点的二叉树是否为平衡二叉树
// 返回左右子树差的绝对值,判断是否为平衡二叉树,返回 -1 or 深度
return abs(leftHeight - rightHeight) > 1 ? -1 : 1 + max(leftHeight, rightHeight);
}
bool isBalanced(TreeNode* root) {
return getHeight(root) == -1 ? false : true;
}
};
递归至二叉树的叶子节点,从下至上计算高度,将孩子节点的信息逐层返回至父节点,同时判断各个二叉树是否为平衡二叉树,如果存在一个非平衡二叉树,返回-1。明确递归的三个步骤:1.确定参数;2.确定终止条件;3.确定基本递归逻辑。一如递归深似海!
给定一个二叉树,返回所有从根节点到叶子节点的路径。说明:叶子节点是指没有子节点的节点。返回类型为vecotr
从图中可以看出,从根节点出发,顺着根左右的顺序:先遍历最左的叶子节点,记录路径;回溯至其最左叶子节点的父节点;遍历其右节点。如此往复,直至遍历整棵树,递归->回溯->递归->回溯->......。
class Solution { // 详细版
private:
void traversal(TreeNode* node, vector& path, vector& result) {
path.push_back(node->val);
if (node->left == nullptr && node->right == nullptr) {
string sPath;
for (int i = 0; i < path.size() - 1; i++) {
sPath += to_string(path[i]);
sPath += "->";
}
sPath += to_string(path[path.size() - 1]);
result.push_back(sPath);
return ;
}
if (node->left) {
traversal(node->left, path, result);
path.pop_back();
}
if (node->right) {
traversal(node->right, path, result);
path.pop_back();
}
}
public:
vector binaryTreePaths(TreeNode* root) {
vector result;
vector path;
if (root == nullptr) return result;
traversal(root, path, result);
return result;
}
};
class Solution { // 精简版
private:
void traversal(TreeNode* cur, string path, vector& result) {
path += to_string(cur->val);
if (cur->left == NULL && cur->right == NULL) {
result.push_back(path);
return;
}
if (cur->left) traversal(cur->left, path + "->", result); // 左
if (cur->right) traversal(cur->right, path + "->", result); // 右
}
public:
vector binaryTreePaths(TreeNode* root) {
vector result;
string path;
if (root == NULL) return result;
traversal(root, path, result);
return result;
}
};
我们选择前序遍历 + 递归的方式解决本题,在详细版的代码中:
1. 明确形参列表,二叉树的根节点、用于记录路径的整型 vector 变量 path、作为结果集的字符串型 vector 变量 result,注意形参列表使用的是引用传递;
2. 明确终止条件,当前节点没有孩子节点时,递归终止;
3.明确基本递归逻辑,当满足终止条件时,将 path 中记录的路径转换为字符串,添加进结果集 result 中,此时我们得到了一条从根节点到叶子节点的路径;然后需要回溯至父节点,查看父节点的其他节点是否还有路径,path 记录了当前的路径,向深处递归时,向 path 中添加元素,回溯时,则从 path 中弹出元素。这个弹出的操作,就对应着回溯。
在精简版的代码中,使用的形参类型和详细版不同,path 没有使用引用传递,而是值传递,意味着在每次递归中,都会创立一个 path 的副本,不同递归次数之间的 path 互不影响,在同一个递归中,path 是相同的。回溯的过程,精简版本的代码隐藏了回溯的过程,对于新手不友好(说的就是我)。
除了递归方法,还可以使用前序遍历 + 迭代的方式解决本题。
class Solution { // 前序遍历,迭代法
public:
vector binaryTreePaths(TreeNode* root) {
stack treeSt; // TreeNode* 类型的栈,用于遍历二叉树
stack pathSt; // string 类型的栈,用于记录当前节点的路径
vector result; // 结果集
if (root == nullptr) return result;
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 == nullptr && node->right == nullptr) result.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 result;
}
};
忽然发现,前序遍历迭代法也有模板,和层序遍历的模板还差不太多,前者使用栈,后者使用队列。treeSt 负责遍历二叉树,pathSt 负责记录路径,如果当前节点为叶子节点,将 pathSt 中的栈顶元素,对应的是从根节点至当前叶子节点的路径,添加进结果集中。
给定二叉树的根节点 root,返回其所有左叶子之和。需要明确,左叶子节点,需要判断当前节点是否为左节点,还需要判断当前节点是否为叶子节点。层序遍历秒了,前序递归法秒了,前序迭代法秒了,前序迭代和层序遍历真的很像。
class Solution { // 层序遍历
public:
int sumOfLeftLeaves(TreeNode* root) {
queue que;
if (root != nullptr) que.push(root);
int sum = 0;
while (!que.empty()) {
int size = que.size();
while (size--) {
TreeNode* node = que.front();
que.pop();
if (node->left) {
que.push(node->left);
if (node->left->left == nullptr && node->left->right == nullptr) sum += node->left->val;
}
if (node->right) que.push(node->right);
}
}
return sum;
}
};
class Solution { // 前序递归
public:
int sumOfLeftLeaves(TreeNode* root) {
if (root == nullptr) return 0;
int leftValue = sumOfLeftLeaves(root->left);
int rightValue = sumOfLeftLeaves(root->right);
if (root->left && !root->left->left && !root->left->right) {
leftValue += root->left->val;
}
int sum = leftValue + rightValue;
return sum;
}
};
class Solution { // 前序迭代
public:
int sumOfLeftLeaves(TreeNode* root) {
stack st;
if (root != nullptr) st.push(root);
int sum = 0;
while (!st.empty()) {
TreeNode* node = st.top();
st.pop();
if (node->right) st.push(node->right);
if (node->left) {
st.push(node->left);
if (node->left->left == nullptr && node->left->right == nullptr) {
sum += node->left->val;
}
}
}
return sum;
}
};