注意:在做二叉树的题目的时候,思考步骤是先确定二叉树的遍历方式,然后就是在遍历的时候如何操作
101. 对称二叉树
题目链接:101. 对称二叉树 - 力扣(LeetCode)
给你一个二叉树的根节点 root
, 检查它是否轴对称。
解题思路:
本题,在递归遍历的过程中,要同时遍历两棵树。比较两个子树的里侧和外侧的元素是否相等。
class Solution {
public:
bool compare(const TreeNode* left, const TreeNode* right) {
if (left == NULL && right == NULL) {
// 左右都为空
return true;
} else if (left == NULL || right == NULL) {
// 左右有空
return false;
} else if (left->val != right->val) {
// 左右都不为空,值不相等
return false;
}
//左右都不为空,且值相等
// 此时才做递归,做下一层的判断
bool outer = compare(left->left,right->right);//外层节点
bool in = compare(left->right,right->left);//内层节点
return outer && in;
}
bool isSymmetric(TreeNode* root) {
if (root == NULL)
return false;
return compare(root->left, root->right);
}
};
572.另一棵树的子树
题目链接:572. 另一棵树的子树 - 力扣(LeetCode)
给你两棵二叉树 root
和 subRoot
。检验 root
中是否包含和 subRoot
具有相同结构和节点值的子树。如果存在,返回 true
;否则,返回 false
。
解题思路:
遍历主树(root
)的每一个节点,对于每个节点,我们都尝试检查该节点是否以子树(subRoot
)为根的子树。
class Solution {
public:
bool isSame(TreeNode* root,TreeNode* subRoot){
if(!root && !subRoot)return true;// 两个节点都为空,则相同
if(!root || !subRoot || root->val != subRoot->val)return false;// 其中一个节点为空,则不相同
// 递归检查左子树和右子树
return isSame(root->left,subRoot->left) && isSame(root->right,subRoot->right);
}
bool isSubtree(TreeNode* root, TreeNode* subRoot) {
if(root == NULL)return false;// 如果s为空,则不包含t
// 检查当前节点为根的子树是否与t相同
if(isSame(root,subRoot))return true;
// 递归地在左子树和右子树中查找
return isSubtree(root->left,subRoot) || isSubtree(root->right,subRoot);
}
};
404.左叶子之和
题目链接:404. 左叶子之和 - 力扣(LeetCode)
给定二叉树的根节点 root
,返回所有左叶子之和。
解题思路:遍历二叉树,累计求和左叶子
关键点在于如何判断左叶子。左叶子 == 左节点 + 叶子节点
叶子节点好判断,左右孩子为空。我们需要如何确保它是左叶子。就必须要通过节点的父节点来判断其左孩子是不是左叶子。
如果该节点的左节点不为空,该节点的左节点的左节点为空,该节点的左节点的右节点为空,则找到了一个左叶子。
class Solution {
public:
int sumOfLeftLeaves(TreeNode* root) {
int sum = 0;
if (root == NULL)
return 0;
// 如果当前节点是左叶子
if (root->left && !root->left->left && !root->left->right) {
sum += root->left->val;// 将该左子节点的值加入总和
}
// 递归地计算左子树和右子树中所有左叶子节点的值之和
sum += sumOfLeftLeaves(root->left);
sum += sumOfLeftLeaves(root->right);
return sum;
}
};
平时我们解二叉树的题目时,已经习惯了通过节点的左右孩子判断本节点的属性,而本题我们要通过节点的父节点判断本节点的属性。
希望通过这道题目,可以扩展大家对二叉树的解题思路。
513.找树左下角的值
题目链接:513. 找树左下角的值 - 力扣(LeetCode)
给定一个二叉树,在树的最后一行找到最左边的值。
解题思路:
注意,本题找到的是最左边的值,不是左叶子。本题采用层次遍历就很容易解决,就不加赘述了。
问题1:如何确保最后一行?
树的最后一行 ,也就是要找深度最大的叶子节点。可以采用前序遍历求二叉树的深度,找到最大深度,然后返回。
问题2:那么如何确保找到的是最左边的叶子节点?
前序遍历是中左右,所以我们是先处理左节点再处理右节点。也就是说在同一层深度中,我们是先访问的左节点,再访问右节点。我们只要保证先左再右,就可以了。
class Solution {
public:
int maxDepth; // 记录最大深度
int firstLeft; // 记录最底层最左边节点的值
void find(TreeNode* cur, int depth) {
if (!cur) // 如果当前节点为空,则直接返回
return;
if (depth > maxDepth) { // 如果当前节点的深度大于已记录的最大深度
maxDepth = depth; // 更新最大深度
firstLeft = cur->val; // 记录当前节点的值作为最底层最左边节点的值
}
// 递归地处理左子树和右子树
find(cur->left, depth + 1);
find(cur->right, depth + 1);
}
int findBottomLeftValue(TreeNode* root) {
maxDepth = INT_MIN; // 初始化最大深度为最小整数值
find(root, 1); // 调用find函数从根节点开始查找
return firstLeft; // 返回最底层最左边节点的值
}
};
106.从中序与后序遍历序列构造二叉树
题目链接:106. 从中序与后序遍历序列构造二叉树 - 力扣(LeetCode)
给定两个整数数组 inorder
和 postorder
,其中 inorder
是二叉树的中序遍历, postorder
是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。
解题思路:
-
后序遍历的特性:后序遍历的顺序是先遍历左子树,然后遍历右子树,最后访问根节点。因此,后序遍历的最后一个元素总是当前遍历子树的根节点。
-
中序遍历的特性:中序遍历的顺序是先遍历左子树,然后访问根节点,最后遍历右子树。这意味着,如果我们知道了根节点在中序遍历中的位置,我们就可以将中序遍历数组分割成左子树和右子树两部分。
详细步骤
-
检查输入:首先检查输入的中序遍历和后序遍历数组是否为空。如果任一为空,则无法构建二叉树,返回
None
。 -
找到根节点:后序遍历的最后一个元素是当前子树的根节点。
-
分割中序遍历:在中序遍历数组中找到根节点的位置,这个位置将中序遍历数组分为左子树和右子树两部分。
-
分割后序遍历:根据中序遍历的分割点,将后序遍历数组(除了最后一个元素)也分割成左右两部分。注意,后序遍历的右子树部分不包括根节点。
-
递归构建:使用分割后的中序遍历和后序遍历数组,递归地构建左子树和右子树。
-
返回根节点:将构建好的左子树和右子树连接到根节点上,并返回根节点。
注意:在分割的时候,确定切割的标准,是左闭右开,还有左开右闭,还是左闭右闭,这个就是不变量,要在递归中保持这个不变量。
class Solution {
public:
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
// 如果后序遍历序列为空,则返回空指针
if (postorder.empty()) return nullptr;
// 创建一个新的树节点,其值为后序遍历序列的最后一个元素(根节点)
TreeNode* root = new TreeNode(postorder.back());
// 如果后序遍历序列只有一个元素,则直接返回创建的根节点
if (postorder.size() == 1) return root;
// 在中序遍历序列中找到根节点的位置
auto index = find(inorder.begin(), inorder.end(), root->val);
// 分割中序遍历序列得到左子树和右子树的中序遍历序列
vector<int> leftInorder(inorder.begin(), index);
vector<int> rightInorder(index + 1, inorder.end());
// 根据左子树的中序遍历序列的长度,在后序遍历序列中分割得到左子树和右子树的后序遍历序列
vector<int> leftPostorder(postorder.begin(), postorder.begin() + leftInorder.size());
vector<int> rightPostorder(postorder.begin() + leftInorder.size(), postorder.end() - 1);
// 递归地构建左子树和右子树
root->left = buildTree(leftInorder, leftPostorder);
root->right = buildTree(rightInorder, rightPostorder);
// 返回根节点
return root;
}
};
补充:
前序和中序可以唯一确定一棵二叉树。
后序和中序可以唯一确定一棵二叉树。
前序和后序不能唯一确定一棵二叉树!,因为没有中序遍历无法确定左右部分,也就是无法分割。
总的来说,中序和前序或者后序两两组合可以确定一棵二叉树。
题外话:本题给我的感觉和101.对称二叉树 有点像,都是在一次遍历中,同时处理左右子树。