今天的题目有下面10道:
- 102.二叉树的层序遍历
- 107.二叉树的层次遍历 II
- 199.二叉树的右视图
- 637.二叉树的层平均值
- 429.N叉树的层序遍历
- 116.填充每个节点的下一个右侧节点指针
- 117.填充每个节点的下一个右侧节点指针II
- 226.翻转二叉树
- 101.对称二叉树
题目比较多,但第2~8道均是在第1道层序遍历的模板基础上根据题目稍作修改即可。每一道题都有递归和迭代2种解法,2种方法都灵活使用还是有难度的。
第1道题(102.二叉树的层序遍历)是二叉树的第4种遍历方式——层序遍历。同样有递归和迭代2种实现方式。迭代方式和广度优先是一样的,用队列实现,只需要将队头节点的val不断放入结果res中,并添加其左右子节点进队列,直到队列空为止,相对较容易。其中需要注意要在内部循环开始前就记录队列的size(),否则在内部循环途中其size()值会动态变化。
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> res;
if (root == nullptr) {
return res;
}
queue<TreeNode*> que;
que.push(root);
while (!que.empty()) {
vector<int> layer;
int size = que.size();
for (int i = 0; i < size; ++i) {
TreeNode *cur = que.front();
que.pop();
layer.push_back(cur->val);
if (cur->left) {
que.push(cur->left);
}
if (cur->right) {
que.push(cur->right);
}
}
res.push_back(layer);
}
return res;
}
};
递归方式的函数参数除了当前节点和用于保存结果的vector之外,还需要当前节点的深度值depth,用来当作vector的下标。递归的终止条件是当前节点为空,之后要先根据depth和vector当前大小来扩展vector(添加vector<int>()),再向vector的第depth个元素中添加当前元素的val,最后再分别递归当前节点的左、右子节点。
class Solution {
public:
void layerOrder(TreeNode *cur, int depth, vector<vector<int>>& res) {
if (cur == nullptr) {
return;
}
if (depth == res.size()) {
res.push_back(vector<int>());
}
res[depth].push_back(cur->val);
layerOrder(cur->left, depth + 1, res);
layerOrder(cur->right, depth + 1, res);
}
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> res;
layerOrder(root, 0, res);
return res;
}
};
第2道题(107.二叉树的层次遍历 II)只需要在第1道题(102.二叉树的层序遍历)基础上将结果vector进行reverse()即可。
class Solution {
public:
vector<vector<int>> levelOrderBottom(TreeNode* root) {
vector<vector<int>> res;
if (root == nullptr) {
return res;
}
queue<TreeNode*> que;
que.push(root);
while (!que.empty()) {
int size = que.size();
vector<int> layer;
for (int i = 0; i < size; i++) {
TreeNode *cur = que.front();
que.pop();
layer.push_back(cur->val);
if (cur->left) {
que.push(cur->left);
}
if (cur->right) {
que.push(cur->right);
}
}
res.push_back(layer);
}
reverse(res.begin(), res.end());
return res;
}
};
第3道题(199.二叉树的右视图)只需要在第1道题(102.二叉树的层序遍历)的基础上将用于保存结果的vector改为仅在每层最后一个元素时才添加val即可。
class Solution {
public:
vector<int> rightSideView(TreeNode* root) {
vector<int> res;
if (root == nullptr) {
return res;
}
queue<TreeNode*> que;
que.push(root);
while (!que.empty()) {
int size = que.size();
for (int i = 0; i < size; ++i) {
TreeNode *cur = que.front();
que.pop();
if (i == size - 1) {
res.push_back(cur->val);
}
if (cur->left) {
que.push(cur->left);
}
if (cur->right) {
que.push(cur->right);
}
}
}
return res;
}
};
第4道题(637.二叉树的层平均值)只需在第1道题(102.二叉树的层序遍历)基础上,在每一层中记录当前层的val之和,并在当前层结束后计算平均值即可。
class Solution {
public:
vector<double> averageOfLevels(TreeNode* root) {
vector<double> res;
if (root == nullptr) {
return res;
}
queue<TreeNode*> que;
que.push(root);
while (!que.empty()) {
int size = que.size();
double sum = 0;
for (int i = 0; i < size; ++i) {
TreeNode *cur = que.front();
que.pop();
sum += (double)cur->val;
if (cur->left) {
que.push(cur->left);
}
if (cur->right) {
que.push(cur->right);
}
}
sum /= size;
res.push_back(sum);
}
return res;
}
};
第5道题(429.N叉树的层序遍历)只需在第1道题(102.二叉树的层序遍历)基础上,在循环中再添加内层循环,用来添加当前节点的所有子节点进队列。
class Solution {
public:
vector<vector<int>> levelOrder(Node* root) {
vector<vector<int>> res;
if (root == nullptr) {
return res;
}
queue<Node*> que;
que.push(root);
while (!que.empty()) {
vector<int> layer;
int size = que.size();
for (int i = 0; i < size; ++i) {
Node *cur = que.front();
que.pop();
layer.push_back(cur->val);
vector<Node*> children = cur->children;
for (int j = 0; j < children.size(); ++j) {
que.push(children[j]);
}
}
res.push_back(layer);
}
return res;
}
};
第6道题(515.在每个树行中找最大值)只需在第1道题(102.二叉树的层序遍历)基础上,在每层中记录当前层的val最大值即可。
class Solution {
public:
vector<int> largestValues(TreeNode* root) {
vector<int> res;
if (root == nullptr) {
return res;
}
queue<TreeNode*> que;
que.push(root);
while (!que.empty()) {
int size = que.size(), maxx = INT_MIN;
for (int i = 0; i < size; ++i) {
TreeNode *cur = que.front();
que.pop();
maxx = max(maxx, cur->val);
if (cur->left) {
que.push(cur->left);
}
if (cur->right) {
que.push(cur->right);
}
}
res.push_back(maxx);
}
return res;
}
};
第7题(116.填充每个节点的下一个右侧节点指针)需在第1道题(102.二叉树的层序遍历)基础上,在内部循环中将每一个节点的next指向下一个节点,也就是当前节点被pop()后的队首元素即可。要注意对每层最后一个元素区别对待,使其next指向NULL。
class Solution {
public:
Node* connect(Node* root) {
if (root == nullptr) {
return root;
}
queue<Node*> que;
que.push(root);
while (!que.empty()) {
int size = que.size();
for (int i = 0; i < size; ++i) {
Node *cur = que.front();
que.pop();
if (cur->left) {
que.push(cur->left);
}
if (cur->right) {
que.push(cur->right);
}
if (i != size - 1) {
cur->next = que.front();
}
else {
cur->next = NULL;
}
}
}
return root;
}
};
二刷:各种树的定义
- 完美二叉树:深度为k,节点数为2^(k + 1) - 1的树;
- 完全二叉树:除最下面一层外其他层是满的,且最后一层靠左分布;
- 满二叉树:每个节点都是0个或2个子节点,没有只有1个子节点的节点。
第8题(117.填充每个节点的下一个右侧节点指针II)与 第7题(116.填充每个节点的下一个右侧节点指针)的区别在于,前者是二叉树,后者更具体,是“完美二叉树”,但在解决方法上没有区别,代码实现与上一题一致。
class Solution {
public:
Node* connect(Node* root) {
if (root == nullptr) {
return root;
}
queue<Node*> que;
que.push(root);
while (!que.empty()) {
int size = que.size();
for (int i = 0; i < size; ++i) {
Node *cur = que.front();
que.pop();
if (cur->left) {
que.push(cur->left);
}
if (cur->right) {
que.push(cur->right);
}
if (i != size - 1) {
cur->next = que.front();
}
else {
cur->next = NULL;
}
}
}
return root;
}
};
第9题(226.翻转二叉树)也分为递归解法和迭代解法。递归解法较简单,分前序、中序、后序遍历3种。前序首先交换当前节点的左、右2个子节点,再分别对左、右子节点进行反转操作;后序遍历首先分别对左、右子节点进行反转操作,再将已经反转好的左、右2个子节点进行交换;中序遍历首先对左子节点进行反转,再将左右子节点交换,注意此时新的左子节点是旧的未经过反转的右子节点,新的右子节点才是旧的已经反转好的左子节点,所以最后一步应该对新的左子节点进行反转操作。3种方式分别如下:
前序递归完整代码:
class Solution {
public:
void invert(TreeNode* cur) {
if (cur == nullptr) {
return;
}
swap(cur->left, cur->right);
invert(cur->left);
invert(cur->right);
}
TreeNode* invertTree(TreeNode* root) {
invert(root);
return root;
}
};
后序递归的invert():
void invert(TreeNode* cur) {
if (cur == nullptr) {
return;
}
invert(cur->left);
invert(cur->right);
swap(cur->left, cur->right);
}
中序递归的的invert():
void invert(TreeNode* cur) {
if (cur == nullptr) {
return;
}
invert(cur->left);
swap(cur->left, cur->right);
invert(cur->left);
}
这道题的迭代方式在有了层序遍历的基础之后也比较容易,只需在层序遍历的过程中对每个当前节点进行左、右子节点的swap()操作即可。
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (root == nullptr) {
return root;
}
queue<TreeNode*> que;
que.push(root);
while (!que.empty()) {
int size = que.size();
for (int i = 0; i < size; ++i) {
TreeNode *cur = que.front();
que.pop();
swap(cur->left, cur->right);
if (cur->left) {
que.push(cur->left);
}
if (cur->right) {
que.push(cur->right);
}
}
}
return root;
}
};
另外,该实现方法中的队列也可换用栈(递归能实现的,栈也能实现),其他地方不变。而该实现方法既是层序遍历,也是一种广度优先的搜索,也可以用深度优先,既迭代方式的前序遍历(非统一方式的即为将上面的队列换成栈,下面代码为统一方式的):
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (root == nullptr) {
return root;
}
stack<TreeNode*> st;
st.push(root);
while (!st.empty()) {
TreeNode *cur = st.top();
st.pop();
if (cur != nullptr) {
if (cur->right) {
st.push(cur->right);
}
if (cur->left) {
st.push(cur->left);
}
st.push(cur);
st.push(nullptr);
}
else {
cur = st.top();
st.pop();
swap(cur->left, cur->right);
}
}
return root;
}
};
第10题(101. 对称二叉树)同样有递归和迭代2种解法。递归法要判断左右子树是否对称,即两个子树的外侧元素、内侧元素是否相等,所以遍历方式只能是后序遍历,但不是绝对的“左->右->中”后序,而是对左子树和右子树分别进行“左->右->中”和“右->左->中”的“后序遍历”。递归函数参数为左、有两子树,需要判断两子树是否空的各种相关情况,两个都空返回true,只有一个空则返回false,其余情况要根据它们内内侧和外侧的对称结果进行判断。
class Solution {
public:
bool areSym(TreeNode *l, TreeNode *r) {
if (l == nullptr && r == nullptr) {
return true;
}
if (l && r && l->val == r->val) {
bool sameOutside = areSym(l->left, r->right);
bool sameInside = areSym(l->right, r->left);
return sameOutside && sameInside;
}
else {
return false;
}
}
bool isSymmetric(TreeNode* root) {
if (root == nullptr) {
return true;
}
return areSym(root->left, root->right);
}
};
迭代法让树的左子树,右子树分别按“中->左->右”的“先序”和“中->右->左”的“先序”将其元素放入队列中,每取出一对,就进行比较,并将其各自的左右/右左节点放入队列中,直到某次比较不相等就返回false,或队列为空就返回true。
class Solution {
public:
bool isSymmetric(TreeNode* root) {
if (root == nullptr) {
return true;
}
queue<TreeNode*> que;
que.push(root->left);
que.push(root->right);
while (!que.empty()) {
TreeNode *cur1 = que.front();
que.pop();
TreeNode *cur2 = que.front();
que.pop();
if (cur1 == nullptr && cur2 == nullptr) {
continue;
}
if (cur1 == nullptr || cur2 == nullptr || cur1->val != cur2->val) {
return false;
}
que.push(cur1->left);
que.push(cur2->right);
que.push(cur1->right);
que.push(cur2->left);
}
return true;
}
};
代码中的队列仅为一个容器,仅要求可以成对放入和按照放入的“对”取出即可,所以替换成栈(其余地方不改)也可以实现。