文章目录
层序遍历汇总
102.二叉树的层序遍历
题目链接🔥🔥
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> result;
queue<TreeNode*> que;
if(root) que.push(root);
while(!que.empty()){
//size代表每一层的元素个数
int size=que.size();
vector<int> vec;
for(int i=0;i<size;i++){
TreeNode* cur=que.front();
vec.push_back(cur->val);
que.pop();
if(cur->left) que.push(cur->left);
if(cur->right) que.push(cur->right);
}
result.push_back(vec);
}
return result;
}
};
复杂度分析
- 时间复杂度
所有节点都会被入队一次且出队一次,需要 O(N) 次操作。
- 空间复杂度
queue中存储元素的个数不会超过n,O(N),
result 向量需要与二叉树中节点数量相同的空间,即 O(N)。
vec 向量用于存储每一层节点的值,在最坏情况下也需要 O(N) 的空间。
学习时间⌛️:20min
107.二叉树的层次遍历II
题目链接🔥🔥
给你二叉树的根节点 root ,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
📎相对于102.二叉树的层序遍历,就是最后把result数组反转一下就可以了。
class Solution {
public:
vector<vector<int>> levelOrderBottom(TreeNode* root) {
vector<vector<int>> result;
queue<TreeNode*> que;
if(root) que.push(root);
while(!que.empty()){
vector<int> vec;
int size=que.size();
while(size--){
TreeNode* cur=que.front();
vec.push_back(cur->val);
que.pop();
if(cur->left) que.push(cur->left);
if(cur->right) que.push(cur->right);
}
result.push_back(vec);
}
reverse(result.begin(),result.end());
return result;
}
};
学习时间⌛️:15min
199.二叉树的右视图
题目链接🔥🔥
给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
📎在遍历每一层的时候,只把每一层的最后一个元素放进result数组。
class Solution {
public:
vector<int> rightSideView(TreeNode* root) {
vector<int> result;
queue<TreeNode*> que;
if(root) que.push(root);
while(!que.empty()){
int size=que.size();
while(size--){
TreeNode* cur=que.front();
que.pop();
if(cur->left) que.push(cur->left);
if(cur->right) que.push(cur->right);
if(size==0) result.push_back(cur->val);
}
}
return result;
}
};
学习时间⌛️:18min
637.二叉树的层平均值
题目链接🔥
给定一个非空二叉树, 返回一个由每层节点平均值组成的数组。
📎在遍历每一层的时候,求一个均值。
class Solution {
public:
vector<double> averageOfLevels(TreeNode* root) {
vector<double> result;
queue<TreeNode*> que;
if(root) 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+=cur->val;
if(cur->left) que.push(cur->left);
if(cur->right) que.push(cur->right);
}
sum=sum/size;
result.push_back(sum);
}
return result;
}
};
学习时间⌛️:10min
429.N叉树的层序遍历
题目链接🔥🔥
给定一个 N 叉树,返回其节点值的层序遍历。 (即从左到右,逐层遍历)。
例如,给定一个 3叉树 :
二叉树的定义:
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) {}
};
N叉数的定义:
class Node {
public:
int val;
vector<Node*> children;
Node() {}
Node(int _val) {
val = _val;
}
Node(int _val, vector<Node*> _children) {
val = _val;
children = _children;
}
};
📎把二叉树的左右节点变成N叉树的遍历 children 的 vector [ i ]
class Solution {
public:
vector<vector<int>> levelOrder(Node* root) {
vector<vector<int>> result;
queue<Node*> que;
if(root) que.push(root);
while(!que.empty()){
int size=que.size();
vector<int> vec;
while(size--){
Node* cur=que.front();
que.pop();
vec.push_back(cur->val);
for(int i=0;i<cur->children.size();i++){
if(cur->children[i]) que.push(cur->children[i]);
}
}
result.push_back(vec);
}
return result;
}
};
学习时间⌛️:19min
515.在每个树行中找最大值
题目链接🔥🔥
需要在二叉树的每一行中找到最大的值。
📎
class Solution {
public:
vector<int> largestValues(TreeNode* root) {
vector<int> result;
queue<TreeNode*> que;
if(root) que.push(root);
while(!que.empty()){
int size=que.size();
int min=INT_MIN;
while(size--){
TreeNode* cur=que.front();
que.pop();
if(cur->val>min) min=cur->val;
if(cur->left) que.push(cur->left);
if(cur->right) que.push(cur->right);
}
result.push_back(min);
}
return result;
}
};
学习时间⌛️:6min
116.填充每个节点的下一个右侧节点指针
题目链接🔥🔥
给定一个完美二叉树,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。
📎如果不是一层当中的最后一个,next指向队列中的下一个,如果是一层当中的最后一个,next指向NULL
class Solution {
public:
Node* connect(Node* root) {
queue<Node*> que;
if(root) que.push(root);
while(!que.empty()){
int size=que.size();
while(size--){
Node* cur=que.front();
que.pop();
if(cur->left) que.push(cur->left);
if(cur->right) que.push(cur->right);
if(size==0) cur->next=NULL;
else cur->next=que.front();
}
}
return root;
}
};
学习时间⌛️:25min
117.填充每个节点的下一个右侧节点指针II
题目链接🔥🔥
📎说是二叉树,但116题目说是完整二叉树,其实没有任何差别,一样的代码
class Solution {
public:
Node* connect(Node* root) {
queue<Node*> que;
if(root) que.push(root);
while(!que.empty()){
int size=que.size();
while(size--){
Node* cur=que.front();
que.pop();
if(cur->left) que.push(cur->left);
if(cur->right) que.push(cur->right);
if(size==0) cur->next=NULL;
else cur->next=que.front();
}
}
return root;
}
};
学习时间⌛️:
104.二叉树的最大深度
题目链接🔥
给定一个二叉树root,返回其最大深度。二叉树的最大深度是指从根节点到最远叶子节点的生存节点上的节点数。
📎一层一层的来遍历二叉树,记录一下遍历的层数就是二叉树的深度
class Solution {
public:
int maxDepth(TreeNode* root) {
queue<TreeNode*> que;
int depth=0;
if(root) que.push(root);
while(!que.empty()){
int size=que.size();
while(size--){
TreeNode* cur=que.front();
que.pop();
if(cur->left) que.push(cur->left);
if(cur->right) que.push(cur->right);
}
depth++;
}
return depth;
}
};
学习时间⌛️:10min
111.二叉树的最小深度
题目链接🔥
📎需要注意的是,只有当左右孩子都为空的时候,才说明遍历的最低点了。如果其中一个孩子为空则不是最低点
class Solution {
public:
int minDepth(TreeNode* root) {
queue<TreeNode*> que;
int depth=0;
if(root) que.push(root);
while(!que.empty()){
int size=que.size();
depth++;
while(size--){
TreeNode* cur=que.front();
que.pop();
if(cur->left) que.push(cur->left);
if(cur->right) que.push(cur->right);
if(!cur->left&&!cur->right) return depth;
}
}
return depth;
}
};
学习时间⌛️:7min
226.翻转二叉树
题目链接🔥
给你一棵二叉树的根节点root,翻转这棵二叉树,并返回其根节点。
示例:
📎想要翻转它,其实就把每一个节点的左右孩子交换一下就可以了。
这道题目使用前序遍历和后序遍历都可以,唯独中序遍历不方便,因为中序遍历会把某些节点的左右孩子翻转了两次!
层序遍历也是可以的。
如果有看不懂的,建议回看二叉树的十种遍历方式
1.递归法(前中后序)
- 前序
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;
}
};
- 后序
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if(root==nullptr) return root;
invertTree(root->left);
invertTree(root->right);
swap(root->left,root->right);
return root;
}
};
- 中序
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if(root==nullptr) return root;
invertTree(root->left);
swap(root->left,root->right);
//还是要翻转左孩子,因为root节点的左右孩子已经换了
invertTree(root->left);
return root;
}
};
2.迭代法(前中后序)
- 前序
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
stack<TreeNode*> st;
if(root) st.push(root);
while(!st.empty()){
TreeNode* cur=st.top();
st.pop();
swap(cur->left,cur->right);
if(cur->right) st.push(cur->right);
if(cur->left) st.push(cur->left);
}
return root;
}
};
- 后序
后序和中序其实一样,顶多把
if(cur->right) st.push(cur->right);
if(cur->left) st.push(cur->left);
两行位置换一下
内部流程中先处理哪个节点再处理哪个节点是没有影响的(递归的中序除外)。
- 中序
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
stack<TreeNode*> st;
TreeNode* cur=root;
while(cur!=nullptr||!st.empty()){
while(cur!=nullptr){
st.push(cur);
cur=cur->left;
}
if(!st.empty()){
cur=st.top();
st.pop();
swap(cur->left,cur->right);
cur=cur->left;
}
}
return root;
}
};
3.统一迭代法(前中后序)
- 前序
lass Solution {
public:
TreeNode* invertTree(TreeNode* root) {
stack<TreeNode*> st;
if(root) st.push(root);
while(!st.empty()){
TreeNode* cur=st.top();
if(cur!=nullptr){
st.pop();
if(cur->right) st.push(cur->right);
if(cur->left) st.push(cur->left);
st.push(cur);
st.push(nullptr);
}
else{
st.pop();
swap(st.top()->left,st.top()->right);
st.pop();
}
}
return root;
}
};
- 后序
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
stack<TreeNode*> st;
if(root) st.push(root);
while(!st.empty()){
TreeNode* cur=st.top();
if(cur!=nullptr){
st.pop();
st.push(cur);
st.push(nullptr);
if(cur->right) st.push(cur->right);
if(cur->left) st.push(cur->left);
}
else{
st.pop();
swap(st.top()->left,st.top()->right);
st.pop();
}
}
return root;
}
};
- 中序
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
stack<TreeNode*> st;
if(root) st.push(root);
while(!st.empty()){
TreeNode* cur=st.top();
if(cur!=nullptr){
st.pop();
if(cur->right) st.push(cur->right);
st.push(cur);
st.push(nullptr);
if(cur->left) st.push(cur->left);
}
else{
st.pop();
swap(st.top()->left,st.top()->right);
st.pop();
}
}
return root;
}
};
4.层序遍历法
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
queue<TreeNode*> que;
if(root) que.push(root);
while(!que.empty()){
int size=que.size();
while(size--){
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;
}
};
总结思考
💡这里和上一篇的遍历的区别就是结果放入result这步换成了swap
💡因为使用递归的中序遍历,某些节点的左右孩子会翻转两次。但使用迭代方式统一写法的中序是可以的。因为这是用栈来遍历,而不是靠指针来遍历,避免了递归法中翻转了两次的情况。
💡做之前要先确定遍历顺序
学习时间⌛️:2h35min
101.对称二叉树
题目链接🔥
给定一个二叉树,检查它是否是镜像对称的。
不能通过判断左右节点是否相等来完成。
左子树和右子树可以相互翻转,看外侧节点和内侧节点是否相同。以在递归遍历的过程中,也是要同时遍历两棵树。
1.递归解法
1.思路分析
- 确定递归函数的参数和返回值
因为要比较的时左右两个子节点,所以参数是两个子节点,返回值是bool类型
bool compare(TreeNode* left, TreeNode* right)
- 确定终止条件
终止的情况有以下几种:
- 左节点为空,右节点不为空,不对称,return false
- 左不为空,右为空,不对称 return false
- 左右都为空,对称,返回true
- 左右都不为空,比较节点数值,不相同就return false
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;
- 确定单层递归的逻辑
当两个节点都不为空且数值相等的情况下才进入单层递归逻辑的处理。
- 比较二叉树外侧是否对称:传入的是左节点的左孩子,右节点的右孩子。
- 比较内测是否对称,传入左节点的右孩子,右节点的左孩子。
- 内外都对称就是true。
bool outside = compare(left->left, right->right); // 左子树:左、 右子树:右
bool inside = compare(left->right, right->left); // 左子树:右、 右子树:左
bool isSame = outside && inside; // 左子树:中、 右子树:中(逻辑处理)
return isSame;
2.代码实现
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);
return outside&&inside;
}
bool isSymmetric(TreeNode* root) {
if(root==nullptr) return true;
return compare(root->left,root->right);
}
};
2.迭代解法(队列)
1.思路分析
用队列迭代和用栈迭代基本一致。用栈迭代类似于二叉树的层序遍历,只是入队顺序由原来的从左到右变为从两侧到中间。把左右两个子树要比较的元素顺序放进一个容器,然后成对成对的取出来进行比较。
2.代码实现
class Solution {
public:
bool isSymmetric(TreeNode* root) {
queue<TreeNode*> que;
if(root==nullptr) return true;
que.push(root->left);
que.push(root->right);
while(!que.empty()){
TreeNode* leftnode=que.front();
que.pop();
TreeNode* rightnode=que.front();
que.pop();
//注意这里左右节点都是空,要continue,不能return,这不是递归
if(leftnode==nullptr&&rightnode==nullptr) continue;
else if(leftnode!=nullptr&&rightnode==nullptr) return false;
else if(leftnode==nullptr&&rightnode!=nullptr) return false;
else if (leftnode->val != rightnode->val) return false;
que.push(leftnode->left);
que.push(rightnode->right);
que.push(leftnode->right);
que.push(rightnode->left);
}
return true;
}
};
2.迭代解法(栈)
1.思路分析
是把左右两个子树要比较的元素顺序放进一个容器,然后成对成对的取出来进行比较,那么其实使用栈也是可以的。
只要把队列原封不动的改成栈就可以了
2.代码实现
class Solution {
public:
bool isSymmetric(TreeNode* root) {
stack<TreeNode*> st;
if(root==nullptr) return true;
st.push(root->left);
st.push(root->right);
while(!st.empty()){
TreeNode* leftnode=st.top();
st.pop();
TreeNode* rightnode=st.top();
st.pop();
//注意这里左右节点都是空,要continue,不能return,这不是递归
if(leftnode==nullptr&&rightnode==nullptr) continue;
else if(leftnode!=nullptr&&rightnode==nullptr) return false;
else if(leftnode==nullptr&&rightnode!=nullptr) return false;
else if (leftnode->val != rightnode->val) return false;
st.push(leftnode->left);
st.push(rightnode->right);
st.push(leftnode->right);
st.push(rightnode->left);
}
return true;
}
};
3.复杂度分析
时间复杂度:O(N)
N为二叉树的节点数目,这里遍历了这颗二叉树,因此时间复杂度为O(N);
空间复杂度:O(N)
这里需要用一个队列/栈来维护节点,每个节点最多进队一次,出队一次,队列中最多不会超过 n 个点,故空间复杂度为 O(n)。
4.总结思考
💡迭代法中我们使用了队列,需要注意的是这不是层序遍历,而且仅仅通过一个容器来成对的存放我们要比较的元素,知道这一本质之后就发现,用队列,用栈,甚至用数组,都是可以的。
学习时间⌛️:1.5h