102.二叉树的层序遍历
即广度优先遍历,这个适合利用队列实现(和深度优先区分,深度优先通过递归方式,或者是利用栈进行迭代来实现)
思路如下图展示:队列先进先出,符合一层一层遍历的逻辑,只需要一层一层入队列,自然能一层一层出队列了。入队列根节点,然后当队列不为空,则需要循环遍历每一层。遍历层中节点的过程则是循环出队列遍历节点,并将出队列节点的子树根节点入队列
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
queue<TreeNode*> que;
if (root != NULL) que.push(root); // 入队列根节点
vector<vector<int>> result;
while (!que.empty()) { // 当队列不为空,则需要进循环遍历每一层
int size = que.size();
vector<int> vec;
// 这里一定要使用固定大小size,不要使用que.size(),因为que.size是不断变化的
// size记录该层节点数
for (int i = 0; i < size; i++) { // 遍历一层中的各个节点
TreeNode* node = que.front();
que.pop();
vec.push_back(node->val);
if (node->left) que.push(node->left);
if (node->right) que.push(node->right); //循环出队列并将出队列节点的左右子树根加入队列
}
result.push_back(vec);
}
return result;
}
};
注意判非空
注意,两重循环的含义。可以这么理解:外层while是准备遍历该层了,然后刚进while需要做一些准备工作,内层for是具体遍历该层所有节点的过程,内层for循环结束后表示准备离开该层了,需要做一些记录该层遍历结果的处理
记住这个代码模板,层序遍历通用
层序遍历模板能够解决以下问题
- 102.二叉树的层序遍历
- 107.二叉树的层次遍历II
- 199.二叉树的右视图
- 637.二叉树的层平均值
- 429.N叉树的层序遍历
- 515.在每个树行中找最大值
- 116.填充每个节点的下一个右侧节点指针
- 117.填充每个节点的下一个右侧节点指针II
- 104.二叉树的最大深度
- 111.二叉树的最小深度
226.翻转二叉树
用递归做
递归把握一个关键点:确定这个递归调用能干什么事
递归三部曲(每一步都考虑一下那个关键点)
- 确定递归的参数和返回值
- 确定终止条件
- 确定单层递归的逻辑(往往想到能不能利用本身递归函数解决子问题)
首先思考递归调用能干什么事:这个递归调用能够翻转以当前节点为根的树
然后递归三部曲
确定参数和返回值:这个递归调用能够翻转以当前节点为根的树,需要传入一个节点表示待翻转的树,返回一个节点表示翻转后的树
TreeNode* invertTree(TreeNode* root);
确定终止条件:这个递归调用能够翻转以当前节点为根的树,当传入的节点为空,直接返回
if (root == NULL) return root;
确定单层递归的逻辑:这个递归调用能够翻转以当前节点为根的树。对于这个根节点,需要交换其 left 和 right 域,然后分别翻转其左右子树,翻转其左右子树的工作可以利用本身递归函数,因为这个递归函数就有这样的能力
整体代码
/**
* 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:
TreeNode* invertTree(TreeNode* root) {
if (root == NULL) return root;
swap(root->left, root->right); // 中
invertTree(root->left); // 左
invertTree(root->right); // 右
return root;
}
};
单层递归逻辑处理中,也可以先分别翻转节点的左右子树,再交换节点的 left 和 right
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (root == NULL) return root;
invertTree(root->left); // 左
invertTree(root->right); // 右
swap(root->left, root->right); // 中
return root;
}
};
但是不能把交换 left 和 right 的逻辑写在翻转左右子树的中间!画图想想原因
本题还能迭代实现,这里重点掌握递归即可。详见文章讲解
101.对称二叉树
这道题定义的递归函数作用是:比较两个树是否为相互翻转的树
比如这两个树就是相互翻转的树
递归三部曲
确定参数和返回值:这个递归调用能够比较两个树是否为相互翻转的树,需要传入两个节点表示待比较的两棵树,返回 bool
bool compare(TreeNode* left, TreeNode* right);
确定终止条件:在某些情况下,能够直接判断出两个树是否为相互翻转的树,并返回
- 左树根节点为空,右树根节点不为空,不对称,return false
- 左树根节点不为空,右树根节点为空,不对称,return false
- 左右树根节点都为空,对称,return true
- 左右树根节点都不为空,但是数值不相等,不对称,return false
确定单层递归的逻辑:当左右树根节点都不为空且值相等时,想要直到这两个树是否为相互翻转的树,需要满足
其中,判断左树的左子树和右树的右子树是否为互为翻转的树能够递归调用函数本身
同理,判断左树的右子树和右树的左子树是否为互为翻转的树能够递归调用函数本身
在上两个条件同时满足时,才能断言左树和右树是互为翻转的树
class Solution {
public:
bool compare(TreeNode* left, TreeNode* right) {
// 首先排除空节点的情况(终止条件)
if (left == NULL && right != NULL) return false;
else if (left != NULL && right == NULL) return false;
else if (left == NULL && right == NULL) return true;
// 排除了空节点,再排除数值不相同的情况
else if (left->val != right->val) return false;
// 此时就是:左右树根节点都不为空,且数值相同的情况
// 此时才开始确定逻辑
bool outside = compare(left->left, right->right); // 左树的左子树、 右树的右子树
bool inside = compare(left->right, right->left); // 左树的右子树、 右树的左子树
bool isSame = outside && inside;
return isSame;
}
bool isSymmetric(TreeNode* root) {
if (root == NULL) return true;
return compare(root->left, root->right);
}
};
回顾总结
层序遍历的模板: 外层while是准备遍历该层了,然后刚进while需要做一些准备工作,内层for是具体遍历该层所有节点的过程,内层for循环结束后表示准备离开该层了,需要做一些记录该层遍历结果的处理
牢记递归三部曲及明确递归函数的作用