1. 理论基础
C++中map、set、multimap,multiset的底层实现都是平衡二叉搜索树,所以map、set的增删操作时间时间复杂度是logn。而unordered_map、unordered_set,unordered_map、unordered_set底层实现是哈希表。
2. 二叉数的递归遍历
class Solution {
public:
void preorder(TreeNode *cur, vector<int> &res) {
if (cur == nullptr)
return;
res.push_back(cur->val);
preorder(cur->left, res);
preorder(cur->right, res);
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> res;
preorder(root, res);
return res;
}
};
class Solution {
public:
void postorder(TreeNode *cur, vector<int> &res) {
if (cur == nullptr)
return;
postorder(cur->left, res);
postorder(cur->right, res);
res.push_back(cur->val);
}
vector<int> postorderTraversal(TreeNode* root) {
vector<int> res;
postorder(root, res);
return res;
}
};
class Solution {
public:
void inorder(TreeNode *cur, vector<int> &res) {
if (cur == nullptr)
return;
inorder(cur->left, res);
res.push_back(cur->val);
inorder(cur->right, res);
}
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
inorder(root, res);
return res;
}
};
3. 二叉树的迭代遍历
思路: 利用栈,将根结点存入栈中。对栈进行遍历,先将栈顶元素的值加入res数组中,在将右左指针依次放入栈中(**先加右孩子的原因是,先入栈的后出栈,遍历时就会后打印)。遍历完成,即可得到先序遍历的数组。
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode*> st;
if (root == nullptr)
return res;
st.push(root);
while (!st.empty()) {
res.push_back(st.top()->val);
TreeNode* cur = st.top();
st.pop(); // 将栈顶元素弹出
// 判断栈顶元素是否存在左右孩子
// 先存右孩子,因为栈为先进后出
if (cur->right != nullptr) st.push(cur->right);
if (cur->left != nullptr) st.push(cur->left);
}
return res;
}
};
思路: 中序遍历是左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点(也就是在把节点的数值放进result数组中),这就造成了处理顺序和访问顺序是不一致的。那么在使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
stack <TreeNode*> st;
TreeNode* cur = root;
while (!st.empty() || cur != nullptr) {
// 先向深遍历左孩子,直到遍历到底层
if (cur != nullptr) {
st.push(cur); // 将当前指针入栈
cur = cur->left; // 左
}
else { // 若当前指针为空
cur = st.top();
st.pop();
res.push_back(cur->val); // 中
cur = cur->right; // 右
}
}
return res;
}
};
思路: 序遍历是中左右,后序遍历是左右中,那么我们只需要调整一下先序遍历的代码顺序,就变成中右左的遍历顺序,然后在反转result数组,输出的结果顺序就是左右中了。
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode*> st;
if (root == nullptr) return res;
st.push(root);
// =》中右左
while (!st.empty()) {
TreeNode* cur = st.top();
st.pop();
res.push_back(cur->val);
// 先加左孩子,再加有孩子,遍历时先遍历右孩子再左孩子
if (cur->left != nullptr) st.push(cur->left);
if (cur->right != nullptr) st.push(cur->right);
}
// 中右左=》左右中
reverse(res.begin(), res.end());
return res;
}
};
4. 二叉树的统一迭代法
思路: 使用空指针标识已被操作过的结点,当遇到空指针时,添加进结果数组。就是要处理的节点放入栈之后,紧接着放入一个空指针作为标记。
关键代码:先/中/后序遍历,只需更改语句的顺序即可
if (cur->right != nullptr) st.push(cur->right);
st.push(cur);
st.push(nullptr);
if (cur->left != nullptr) st.push(cur->left);
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode*> st;
if (root != nullptr) st.push(root);
while (!st.empty()) {
TreeNode* cur = st.top(); // 记录栈顶指针
if (cur != nullptr) { // 当栈顶指针非空时,添加其左右孩子结点
st.pop(); // 弹出栈顶结点,防止重复操作
// 当右孩子非空时,添加进栈
if (cur->right != nullptr) st.push(cur->right);
st.push(cur); // 再将中间结点入栈
st.push(nullptr); // 添加空指针,表示该结点已添加左右孩子
// 当左孩子非空时,添加进栈
if (cur->left != nullptr) st.push(cur->left);
}
else { // 当遇到空结点时,即碰到表示中间结点已被标识
st.pop();
cur = st.top();
st.pop();
res.push_back(cur->val); // 将中间结点的值添加
}
}
return res;
}
};
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode*> st;
if (root != nullptr) st.push(root);
while (!st.empty()) {
TreeNode* cur = st.top(); // 记录栈顶指针
if (cur != nullptr) { // 当栈顶指针非空时,添加其左右孩子结点
st.pop(); // 弹出栈顶结点,防止重复操作
// 当右孩子非空时,添加进栈
if (cur->right != nullptr) st.push(cur->right);
// 当左孩子非空时,添加进栈
if (cur->left != nullptr) st.push(cur->left);
st.push(cur); // 再将中间结点入栈
st.push(nullptr); // 添加空指针,表示该结点已添加左右孩子
}
else { // 当遇到空结点时,即碰到表示中间结点已被标识
st.pop();
cur = st.top();
st.pop();
res.push_back(cur->val); // 将中间结点的值添加
}
}
return res;
}
};
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode*> st;
if (root != nullptr) st.push(root);
while (!st.empty()) {
TreeNode* cur = st.top(); // 记录栈顶指针
if (cur != nullptr) { // 当栈顶指针非空时,添加其左右孩子结点
st.pop(); // 弹出栈顶结点,防止重复操作
st.push(cur); // 再将中间结点入栈
st.push(nullptr); // 添加空指针,表示该结点已添加左右孩子
// 当右孩子非空时,添加进栈
if (cur->right != nullptr) st.push(cur->right);
// 当左孩子非空时,添加进栈
if (cur->left != nullptr) st.push(cur->left);
}
else { // 当遇到空结点时,即碰到表示中间结点已被标识
st.pop();
cur = st.top();
st.pop();
res.push_back(cur->val); // 将中间结点的值添加
}
}
return res;
}
};
5. 二叉树的层序遍历
- 二叉树的层序遍历
思路: 利用队列将每个结点的值加入结果中,并将该结点的左右孩子加入队列。对整个队列进行遍历,直至遍历完所有结点。
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> res;
queue<TreeNode*> que;
if (root == NULL) return res;
que.push(root);
while (!que.empty()) {
int size = que.size(); // 使用size确定当前层的结点个数
vector<int> vec; // 添加当前层的结果
for (int i = 0; i < size; i++) {
TreeNode* cur = que.front();
que.pop();
vec.push_back(cur->val);// 添加结点的val
// 将左右孩子入队
if (cur->left != NULL) que.push(cur->left);
if (cur->right != NULL) que.push(cur->right);
}
res.push_back(vec);
}
return res;
}
};
时间复杂度: O(n)
空间复杂度: O(n)
- 二叉树的层序遍历Ⅱ
思路: 将层序遍历的结果直接反转即可
class Solution {
public:
vector<vector<int>> levelOrderBottom(TreeNode* root) {
vector<vector<int>> res;
queue<TreeNode*> que;
if (root == NULL) return res;
que.push(root);
while (!que.empty()) {
int size = que.size(); // 使用size确定当前层的结点个数
vector<int> vec; // 添加当前层的结果
for (int i = 0; i < size; i++) {
TreeNode* cur = que.front();
que.pop();
vec.push_back(cur->val);// 添加结点的val
// 将左右孩子入队
if (cur->left != NULL) que.push(cur->left);
if (cur->right != NULL) que.push(cur->right);
}
res.push_back(vec);
}
reverse(res.begin(), res.end());
return res;
}
};
- 二叉树的右视图
思路: 再二叉树的层序遍历的基础上,判断当前元素是否为本层的末尾元素即可。
class Solution {
public:
vector<int> rightSideView(TreeNode* root) {
vector<int> res;
queue<TreeNode*> que;
if (root == NULL) return res;
que.push(root);
while (!que.empty()) {
int size = que.size();
int value = 0;
for (int i = 0; i < size; i++) {
TreeNode* cur = que.front();
que.pop();
value = cur->val;
if (cur->left != NULL) que.push(cur->left);
if (cur->right != NULL) que.push(cur->right);
}
res.push_back(value); // 添加末尾元素
}
return res;
}
};
- 二叉树的平均值
思路: 将层序遍历中的每层元素加入一个数组改为加和为一个值,添加进一个总数组中即可。
class Solution {
public:
vector<double> averageOfLevels(TreeNode* root) {
vector<double> res;
queue<TreeNode*> que;
if (root == NULL) return res;
que.push(root);
while (!que.empty()) {
int size = que.size();
double value = 0;
for (int i = 0; i < size; i++) {
TreeNode* cur = que.front();
que.pop();
value += cur->val; // 计算本层的元素和
if (cur->left != NULL) que.push(cur->left);
if (cur->right != NULL) que.push(cur->right);
}
res.push_back(value / size); // 本层的均值
}
return res;
}
};
- N叉树的层序遍历
思路: 与层序遍历的解法相似,只不过将添加左右孩子改为添加全部孩子。
class Solution {
public:
vector<vector<int>> levelOrder(Node* root) {
vector<vector<int>> res;
queue<Node*> que;
if (root == NULL) return res;
que.push(root);
while (!que.empty()) {
int size = que.size();
vector<int> vec;
for (int i = 0; i < size; i++) {
Node* cur = que.front();
que.pop();
vec.push_back(cur->val); // 加入当前结点的值
// 将孩子添加进队列
for (int j = 0; j < cur->children.size(); j++)
if (cur->children[j] != NULL)
que.push(cur->children[j]);
}
res.push_back(vec);
}
return res;
}
};
- 在每个树行中找最大值
思路: 与求二叉树的右视图类似,只不过将求每行最右元素改为求每行最大值。在for循环中比较即可。
class Solution {
public:
vector<int> largestValues(TreeNode* root) {
vector<int> res;
queue<TreeNode*> que;
if (root == NULL) return res;
que.push(root);
while (!que.empty()) {
int size = que.size();
int max = INT_MIN;
for (int i = 0; i < size; i++) {
TreeNode* cur = que.front();
que.pop();
if (max < cur->val)
max = cur->val;
if (cur->left != NULL) que.push(cur->left);
if (cur->right != NULL) que.push(cur->right);
}
res.push_back(max); // 添加末尾元素
}
return res;
}
};
- 填充每个节点的下一个右侧节点
思路: 在层序遍历的基础,判断当前节点是否是 本层的末尾节点,若是,则不进行操作;若不是,则添加next。
class Solution {
public:
Node* connect(Node* root) {
queue<Node*> que;
if (root == NULL) return root;
que.push(root);
while (!que.empty()) {
int size = que.size();
for (int i = 0; i < size; i++) {
Node* cur = que.front();
que.pop();
// 当该结点非当层的末尾结点,且还有剩余元素时,添加next
if (!que.empty() && i + 1 < size)
cur->next = que.front();
if (cur->left != NULL) que.push(cur->left);
if (cur->right != NULL) que.push(cur->right);
}
}
return root;
}
};
- 填充每个节点的下一个右侧节点Ⅱ
思路: 与上一题的思路相同
class Solution {
public:
Node* connect(Node* root) {
queue<Node*> que;
if (root == NULL) return root;
que.push(root);
while (!que.empty()) {
int size = que.size();
for (int i = 0; i < size; i++) {
Node* cur = que.front();
que.pop();
// 当该结点非当层的末尾结点,且还有剩余元素时,添加next
if (!que.empty() && i + 1 < size)
cur->next = que.front();
if (cur->left != NULL) que.push(cur->left);
if (cur->right != NULL) que.push(cur->right);
}
}
return root;
}
};
- 二叉树的最大深度
思路: 只需要一个遍历在层序遍历的过程中记录深度即可
class Solution {
public:
int maxDepth(TreeNode* root) {
int depth = 0;
queue<TreeNode*> que;
if (root == NULL) return depth;
que.push(root);
while (!que.empty()) {
int size = que.size();
depth++;
for (int i = 0; i < size; i++) {
TreeNode* cur = que.front();
que.pop();
if (cur->left != NULL) que.push(cur->left);
if (cur->right != NULL) que.push(cur->right);
}
}
return depth;
}
};
- 二叉树的最小深度
思路: 与最大深度相似,但需要判断当前节点是否有左右孩子,若左右孩子均无,则直接返回当前深度,当前深度即为最小深度。
class Solution {
public:
int minDepth(TreeNode* root) {
int depth = 0;
queue<TreeNode*> que;
if (root == NULL) return depth;
que.push(root);
while (!que.empty()) {
int size = que.size();
depth++;
for (int i = 0; i < size; i++) {
TreeNode* cur = que.front();
que.pop();
if (cur->left != NULL) que.push(cur->left);
if (cur->right != NULL) que.push(cur->right);
// 当左右孩子均为空时,返回当前深度
if (cur->left == NULL && cur->right == NULL) return depth;
}
}
return depth;
}
};
- 总结
二叉树的层序遍历,就是图论中的广度优先搜索在二叉树中的应用,需要借助队列来实现
6. 翻转二叉树
思路: 翻转二叉树,即将每个二叉树的左右孩子翻转,即可得到整个二叉树翻转的结果
层序遍历:
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (root == NULL) 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 != NULL) que.push(cur->left);
if (cur->right != NULL) que.push(cur->right);
}
}
return root;
}
};
先序遍历(递归法):
class Solution {
public:
void preorder(TreeNode* T) {
if (T == NULL) return;
// 翻转左右孩子
swap(T->left, T->right);
preorder(T->left);
preorder(T->right);
}
TreeNode* invertTree(TreeNode* root) {
if (root == NULL) return root;
preorder(root);
return root;
}
};
先序遍历(迭代法):
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (root == NULL) return root;
stack<TreeNode*> st;
st.push(root);
while (!st.empty()) {
TreeNode* cur = st.top();
swap(cur->right, cur->left);
st.pop();
// 先加右孩子,后加左孩子
if(cur->right!= NULL) st.push(cur->right);
if (cur->left != NULL) st.push(cur->left);
}
return root;
}
};
时间复杂度: O(n)