参考
1. 代码随想录
提纲:
0. 基础知识(参考:二叉树理论基础篇)
二叉树的种类
在我们解题过程中二叉树有两种主要的形式:满二叉树和完全二叉树。
- 满二叉树
- 完全二叉树
除了最底层节点可能没填满外,其余每层节点数都达到最大值,
并且最下面一层的节点都集中于左边。 - 二叉搜索树
若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
它的左、右子树也分别为二叉排序树 - 平衡二叉搜索树
它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,
并且左右两个子树都是一棵平衡二叉树。
C++中
堆 的底层实现是完全二叉树
map、set、multimap,multiset的底层实现都是平衡二叉搜索树,所以map、set的增删操作时间时间复杂度是logn,
unordered_map、unordered_set底层实现是哈希表。
二叉树的存储方式
二叉树可以链式存储,也可以顺序存储。
-
链式存储方式就用指针
一般我们都是用链式存储二叉树 -
顺序存储的方式就是用数组
如果父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2
二叉数的定义
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
1. 二叉树的遍历方式
二叉树主要有两种遍历方式:
- 深度优先遍历:先往深走,遇到叶子节点再往回走。
前序遍历(递归法,迭代法)中左右
中序遍历(递归法,迭代法)左中右
后序遍历(递归法,迭代法)左右中
- 广度优先遍历:一层一层的去遍历。
层次遍历(迭代法)
2. 递归法
2.1 前序
class Solution {
public:
void traversal(TreeNode* cur, vector<int>& vec) {
if (cur == NULL) return;
vec.push_back(cur->val); // 中
traversal(cur->left, vec); // 左
traversal(cur->right, vec); // 右
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> result;
traversal(root, result);
return result;
}
};
2.2 中序
void traversal(TreeNode* cur, vector<int>& vec) {
if (cur == NULL) return;
traversal(cur->left, vec); // 左
vec.push_back(cur->val); // 中
traversal(cur->right, vec); // 右
}
2.3 后序
void traversal(TreeNode* cur, vector<int>& vec) {
if (cur == NULL) return;
traversal(cur->left, vec); // 左
traversal(cur->right, vec); // 右
vec.push_back(cur->val); // 中
}
3. 迭代法
3.1 前序
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> result;
if (root == NULL) return result;
st.push(root);
while (!st.empty()) {
TreeNode* node = st.top(); // 中
st.pop();
result.push_back(node->val);
if (node->right) st.push(node->right); // 右(空节点不入栈)
if (node->left) st.push(node->left); // 左(空节点不入栈)
}
return result;
}
};
3.2 后序
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> result;
if (root == NULL) return result;
st.push(root);
while (!st.empty()) {
TreeNode* node = st.top();
st.pop();
result.push_back(node->val);
/*-------- 改为 中 右 左 --------*/
if (node->left) st.push(node->left); // 相对于前序遍历,这更改一下入栈顺序 (空节点不入栈)
if (node->right) st.push(node->right); // 空节点不入栈
}
/*-------- 调换 --------*/
reverse(result.begin(), result.end()); // 将结果反转之后就是左右中的顺序了
return result;
}
};
3.3 中序
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
TreeNode* cur = root;
while (cur != NULL || !st.empty()) {
if (cur != NULL) { // 指针来访问节点,访问到最底层
st.push(cur); // 将访问的节点放进栈
cur = cur->left; // 左
} else {
cur = st.top(); // 从栈里弹出的数据,就是要处理的数据(放进result数组里的数据)
st.pop();
result.push_back(cur->val); // 中
cur = cur->right; // 右
}
}
return result;
}
};
4. 统一迭代法
理解:
- top() = 5进来,被弹出,stack: 空
- 进行一次右中左,stack: 6, 5, N, 4
- top() = 4 进来,被弹出, stack: 6, 5, N,
- 进行一次右中左,stack: 6, 5, N,2,4,N, 1
- top() = 1 进来,被弹出, stack: 6, 5, N,2,4,N,
- 进行一次右中左,stack: 6, 5, N,2,4,N, 1,N
- 进行到else分支:
- N 弹出,1 节点取中间val, 并弹出
stack: 6, 5, N,2,4,N
res: 1 - N 弹出,4 节点取中间val, 并弹出
stack: 6, 5, N,2
res: 1,4 - 再次进入if 分支,因为top() != NULL, 进行一次弹出并右中空左
stack: 6, 5, N,2
stack: 6, 5, N,
stack: 6, 5, N,2, N - N 弹出,2 节点取中间val, 并弹出
stack: 6, 5, N
res: 1,4, 2 - N 弹出,5 节点取中间val, 并弹出
stack: 6,
res: 1,4, 2, 5 - 再次进入if 分支,因为top() != NULL, 进行一次弹出并右中空左
stack: 6
stack:
stack: 6, N - N 弹出,6 节点取中间val, 并弹出
- Res: 1,4, 2, 5,6
4.1 中序: 左中右
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
if (root != NULL) st.push(root);
while (!st.empty()) {
TreeNode* node = st.top();
if (node != NULL) {
st.pop();
// 将该节点弹出,避免重复操作,下面再将右中左节点添加到栈中
/*------ 中序:左中右, 代码顺序:右中左 ------*/
if (node->right) st.push(node->right); // 添加右节点(空节点不入栈)
st.push(node); // 添加中节点
st.push(NULL); // 中节点访问过,但是还没有处理,加入空节点做为标记。
if (node->left) st.push(node->left); // 添加左节点(空节点不入栈)
/*-------------------------------------------*/
}
// 只有遇到空节点的时候,才将下一个节点放进结果集
else {
st.pop(); // 将空节点弹出
node = st.top(); // 重新取出栈中元素
st.pop();
result.push_back(node->val); // 加入到结果集
}
}
return result;
}
};
4.2 前序: 中左右
/*------ 前序:中左右, 代码顺序:右左中 ------*/
if (node->right) st.push(node->right); // 添加右节点(空节点不入栈)
if (node->left) st.push(node->left); // 添加左节点(空节点不入栈)
st.push(node); // 添加中节点
st.push(NULL); // 中节点访问过,但是还没有处理,加入空节点做为标记。
/*-------------------------------------------*/
4.3 后序: 左右中
/*------ 前序:左右中, 代码顺序:中右左 ------*/
st.push(node); // 添加中节点
st.push(NULL); // 中节点访问过,但是还没有处理,加入空节点做为标记。
if (node->right) st.push(node->right); // 添加右节点(空节点不入栈)
if (node->left) st.push(node->left); // 添加左节点(空节点不入栈)
/*-------------------------------------------*/
5. 层序遍历
题目:
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(); // 固定size
vector<int> vec;
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;
}
};
107.二叉树的层次遍历II
while(!que.empty()){
······
}
reverse(result.begin(),result.end());
return result;
199.二叉树的右视图
while (!que.empty()) {
int size = que.size();
for (int i = 0; i < size; i++) {
TreeNode* node = que.front();
que.pop();
if (i == (size - 1)) result.push_back(node->val);
// 将每一层的最后元素放入result数组中
if (node->left) que.push(node->left);
if (node->right) que.push(node->right);
}
}
637.二叉树的层平均值
while(!que.empty()){
int size = que.size();
// vector<double> vec;
// 不增加vector, 直接定义sum变量
double sum=0;
for(int i=0;i<size;i++){
TreeNode* node = que.front();
que.pop();
// vec.push_back(node->val / double(size));
sum += node->val;
if(node->left) que.push(node -> left);
if(node->right) que.push(node -> right);
}
// double temp=0;
// for(int i=0;i<size;i++){
// temp+=vec[i];
// }
// result.push_back(temp);
result.push_back(sum/size);
}
429.N叉树的层序遍历
for(int i=0; i<size; i++){
Node* node=que.front();
que.pop();
res.push_back(node->val);
// 转到孩子节点
for(int i=0; i< node->children.size();i++){
if(node->children[i]) que.push(node->children[i]);
}
}
515.在每个树行中找最大值
while(!que.empty()){
int size = que.size();
int temp = INT_MIN; // 定义负无穷大,正无穷大为INT_MAX
for(int i=0; i<size; i++){
TreeNode* node =que.front();
temp = node->val > temp ? node->val: temp ;
que.pop();
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
result.push_back(temp);
}
116.填充每个节点的下一个右侧节点指针
理解:
- 最顶层
i=0
que: 1进入后,弹出
nodePre: 1
node:1
que:2,3进入,
最后1 -> next =NULL - 第二层
i=0
que: 2,3, 弹出2,
nodePre: 2
node: 2
que: 4,5进入
i=1
que: 3,4,5,弹出 3
node: 3
nodePre->next = node; 即 2-> next =3
nodePre: 3
que:4,5 ,同时,6,7进入
最后3 -> next =NULL - 第三层
i=0
que: 4,5,6,7, 弹出4,
nodePre: 4
node: 4
que:5,6,7
i=1
que: 5,6,7 弹出 5
node: 5
nodePre->next = node; 即 4-> next = 5
nodePre: 5
que:6,7
i=2
que: 6,7 弹出 6
node: 6
nodePre->next = node; 即 5-> next = 6
nodePre: 6
que:7
i=3
que: 7 弹出 7
node: 7
nodePre->next = node; 即 6-> next = 7
nodePre: 7
que:空
最后,7 -> next =NULL
class Solution {
public:
Node* connect(Node* root) {
// Node* res;
queue<Node*> que;
if(root!=NULL) que.push(root);
while(!que.empty()){
int size = que.size();
// vector<int> temp;
Node* nodePre;
Node* node;
for(int i=0; i<size; i++){
// Node* node = que.front();
// que.pop();
// res.push_back(node->val);
// if(i == size-1) res.push_back('#');
if(i==0){
nodePre = que.front(); // 取出一层的头结点
que.pop();
node = nodePre;
}else{
node = que.front();
que.pop();
nodePre -> next = node;
nodePre = nodePre->next;
}
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
nodePre-> next =NULL;
}
// return res;
return root;
}
};
104.二叉树的最大深度
使用迭代法的话,使用层序遍历是最为合适的,因为最大的深度就是二叉树的层数,和层序遍历的方式极其吻合。
在二叉树中,一层一层的来遍历二叉树,记录一下遍历的层数就是二叉树的深度
可以使用二叉树层序遍历的模板来解决的
class Solution {
public:
int maxDepth(TreeNode* root) {
// f(node-> left || node->right) depth++; 错误的想法
if(root == NULL ) return 0;
int depth = 0;
queue<TreeNode*> que;
que.push(root);
while(!que.empty()) {
int size = que.size();
depth++; // 记录深度
for (int i = 0; i < size; i++) {
TreeNode* node = que.front();
que.pop();
if (node->left) que.push(node->left);
if (node->right) que.push(node->right);
}
}
return depth;
}
};
111.二叉树的最小深度
基本同上
while(!que.empty()){
int size = que.size();
count++;
for(int i=0; i<size;i++){
TreeNode* node = que.front();
que.pop();
// 或者 if(!(node->left || node->right )) return count;
if (!node->left && !node->right) return count;
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
}
翻转二叉树
首先想好,用递归还是迭代,再者,用什么方式遍历
递归:
-
递归三部曲:
1.确定递归函数的参数和返回值
2.确定终止条件
3.确定单层递归的逻辑/* 递归法 */ class Solution { public: TreeNode* invertTree(TreeNode* root) { // 首先确定递归的终止条件 if(root == NULL ) return root; // 前序遍历:中左右 swap(root-> left, root->right); invertTree(root->left); invertTree(root->right); // 后序遍历:左右中 // invertTree(root->left); // invertTree(root->right); // swap(root-> left, root->right); return root; } };
迭代:
-
深度优先遍历:
class Solution { public: TreeNode* invertTree(TreeNode* root) { if (root == NULL) return root; stack<TreeNode*> st; st.push(root); while(!st.empty()) { TreeNode* node = st.top(); // 中 st.pop(); swap(node->left, node->right); if(node->right) st.push(node->right); // 右 if(node->left) st.push(node->left); // 左 } return root; } }; /* 统一 写法 */ class Solution { public: TreeNode* invertTree(TreeNode* root) { stack<TreeNode*> st; if (root != NULL) st.push(root); while (!st.empty()) { TreeNode* node = st.top(); if (node != NULL) { st.pop(); if (node->right) st.push(node->right); // 右 if (node->left) st.push(node->left); // 左 st.push(node); // 中 st.push(NULL); } else { st.pop(); node = st.top(); st.pop(); swap(node->left, node->right); // 节点处理逻辑 } } return root; } };
-
广度优先遍历:层序遍历
class Solution { public: TreeNode* invertTree(TreeNode* root) { queue<TreeNode*> que; if (root != NULL) que.push(root); while (!que.empty()) { int size = que.size(); for (int i = 0; i < size; i++) { TreeNode* node = que.front(); que.pop(); swap(node->left, node->right); // 节点处理 if (node->left) que.push(node->left); if (node->right) que.push(node->right); } } return root; } };