先搞定树。
学习代码随想录 (programmercarl.com),感谢大佬的分享。资料pdf见大佬的公众号。
要说是完全原创没有借鉴有点牵强,但也不能说完全不是原创。
[抄答案的时候发现本地visual studio 2019运行不了,关于tie,tuple的部分有红线。baidu后发现是我的c++版本太低。
]
设置成最新就好了。
记录一些知识点:
满二叉树:深度为k,有(2^k)-1个节点的树。它只有度为0和2的结点,并且度为0的结点在同一层。
完全二叉树:除了 最底层节点 可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。
二叉搜索树:是一个有序树,数值有顺序。
- 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
- 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
- 它的左、右子树也分别为二叉搜索树。
平衡二叉搜索树:又被称为AVL(Adelson-Velsky and Landis)树。它是空树或者左右两个子树的高度差不超过1,且左右子树都是平衡二叉搜索树。
C++中map、set、multimap,multiset的底层实现都是平衡二叉搜索树,所以map、set的增删操作时间时间复杂度是log(n).
unordered_map、unordered_set,unordered_map、unordered_map底层实现是哈希表。
二叉树的存储方式
二叉树可以链式存储,也可以顺序存储。
那么链式存储方式就用指针, 顺序存储的方式就是用数组。
二叉树的遍历方式
主要有两种遍历方式:
1.深度优先遍历:先往深走,遇到叶子节点再往回走。
- 深度优先遍历dfs
- 前序遍历(递归法,stack迭代法)
- 中序遍历(递归法,stack迭代法)
- 后序遍历(递归法,stack迭代法)
每次写递归,都按照这三要素来写,可以保证大家写出正确的递归算法!
-
确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。[是否需要辅助函数。]
-
确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。[最基本的最简单的情况,并返回。]
-
确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。[单层需要做什么。有时候需要回溯。!]
//递归法
class Solution_digui {//递归法
public:
vector<int> result;
void preorder(TreeNode* cur, vector<int>& vec) {//前序遍历
if (cur == NULL) return;
vec.push_back(cur->val); // 中
preorder(cur->left, vec); // 左
preorder(cur->right, vec); // 右
}
void midorder(TreeNode* cur, vector<int>& vec) {//中序遍历
if (cur == NULL) return;
midorder(cur->left, vec); // 左
vec.push_back(cur->val); // 中
midorder(cur->right, vec); // 右
}
void postorder(TreeNode* cur, vector<int>& vec) {//后序遍历
if (cur == NULL) return;
postorder(cur->left, vec); // 左
postorder(cur->right, vec); // 右
vec.push_back(cur->val); // 中
}
};
每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。
用栈迭代实现二叉树的前后中序遍历了。
先序遍历是中左右,后续遍历是左右中。当我们调整先序遍历的代码顺序,就变成中右左的遍历顺序,然后再反转result数组,输出的结果顺序就是左右中了,如下图:(如此可把先序改成后序)
//迭代法
class Solution_diedai {//迭代法
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); // 左(空节点不入栈)
}//入栈顺序,中右左、右左、右左。
//res顺序。中左左左左右右右右。//5,4,1,2,6;stack先入后出;queue先入先出。
return result;
}
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;
}
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;
}
};
2.广度优先遍历:一层一层的去遍历。
- 广度优先遍历bfs
- 层次遍历(queue迭代法)
//层序遍历
class Solution_cengxu {//层序遍历
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是不断变化的
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;
}
};
---------------------------------
- 前序遍历:中左右
- 中序遍历:左中右
- 后序遍历:左右中
前面大佬讲过讲栈与队列,他说:栈其实就是递归的一种是实现结构,也就说前中后序遍历的逻辑其实都是可以借助栈(先入后出)使用 非递归的方式来实现的。
而广度优先遍历的实现一般使用队列queue来实现,这也是队列先进先出的特点所决定的,因为需要先进先出的结构,才能一层一层的来遍历二叉树。
接下来是做题.
看了随想录大佬的总结后得知,BST的插入比较简单(找空节点插入),删除比较难,它难在第五种情况。
1:没找到删除的节点,遍历到空节点直接返回了;
2:找到。左右孩⼦都为空(叶⼦节点),直接删除节点, 返回NULL为根节点
3:找到。删除节点的左孩⼦为空,右孩⼦不为空,删除节点,右孩⼦补位,返回右孩⼦为根节点
4:找到。删除节点的右孩⼦为空,左孩⼦不为空,删除节点,左孩⼦补位,返回左孩⼦为根节点
5:找到。左右孩⼦节点都不为空,则将删除节点的左⼦树头结点(左孩⼦)放到删除节点的右⼦树 的最左⾯节点的左孩⼦上,返回删除节点右孩⼦为新的根节点。
----------好做的树题目,不限于各种遍历(迭代、递归;前中后序;DFS、BFS)、深度、高度、公共祖先;关于树的插入、删除、构造,属于上升难度的题目,但也跑不出基本的树的操作。
之前做过力扣的一部分题,现在从牛客开始练习。
好题,重建二叉树,根据前序和中序,牛客NC12 重建二叉树
输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6}
class Solution {
public://大佬 Peterliang 的答案//https://blog.nowcoder.net/17fighting8
TreeNode* root;
// 定义一个全局变量,减少参数的传递
vector<int> p,i;
TreeNode* build(int lp,int rp,int lv,int rv){
// 不满足区间的情况
if(lp>rp) return NULL;
TreeNode* now=new TreeNode(p[lp]);
int index=lv; // 这个变量用来查找在中序遍历里面根节点的下标
while(index<=rv&&p[lp]!=i[index]){
index++;
}
int len=index-lv;
// 递归查找左节点
now->left=build(lp+1,lp+len,lv,index-1);
// 递归查找右节点
now->right=build(lp+len+1,rp,index+1,rv);
return now;
}
TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
p=pre,i=vin;
TreeNode* root=build(0,pre.size()-1,0,vin.size()-1);
return root;
}
};
变型
答案:摘自大佬 牛客499819205号
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
* 求二叉树的右视图
* @param xianxu int整型vector 先序遍历
* @param zhongxu int整型vector 中序遍历
* @return int整型vector
*/
// struct TreeNode{
// int val;
// TreeNode * left;
// TreeNode * right;
// };
vector<int> solve(vector<int>& xianxu, vector<int>& zhongxu) {
// write code here
queue<TreeNode*> q;
int n = xianxu.size();
// vector<int> res;
// TreeNode* root = buildTree(xianxu,zhongxu, 0, n-1, 0, n-1);
// if(root == NULL) return res;
// q.push(root);
// while(!q.empty())
// {
// int m = q.size();
// res.push_back(q.back()->val);
// for(int i = 0; i < m; i++)
// {
// TreeNode* n = q.front();
// if(n->left != NULL) q.push(n->left);
// if(n->right!= NULL) q.push(n->right);
// q.pop();
// }
// }
getRightView(xianxu,zhongxu, 0, n-1, 0, n-1,0);
return res2;
}
// TreeNode * buildTree(vector<int>& pre, vector<int>& in, int pre_start, int pre_end, int in_start, int in_end)
// {
// if(pre_start > pre_end) return NULL;
// int key = pre[pre_start];
// TreeNode* curr = new TreeNode();
// curr->val = key;
// int i = in_start;
// while(in[i] != key) i++;
// int l_left = i-in_start, l_right = in_end - i;
// curr->left = buildTree(pre,in,pre_start+1,l_left+pre_start,in_start,i-1);
// curr->right = buildTree(pre,in,l_left+pre_start+1,pre_end,i+1,in_end);
// return curr;
// }
void getRightView(vector<int>& pre, vector<int>& in, int pre_start, int pre_end, int in_start, int in_end, int level)
{
if(pre_start > pre_end) return;
int key = pre[pre_start];
if(level == res2.size())
res2.push_back(key);
int i = in_start;
while(in[i] != key) i++;
int l_left = i-in_start, l_right = in_end - i;
getRightView(pre,in,l_left+pre_start+1,pre_end,i+1,in_end,level+1);
getRightView(pre,in,pre_start+1,l_left+pre_start,in_start,i-1,level+1);
}
private:
vector<int> res2;
};
538. 把二叉搜索树转换为累加树 - 力扣https://leetcode-cn.com/problems/convert-bst-to-greater-tree/ 这个题也不错.
给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。
提醒一下,二叉搜索树满足下列约束条件:
·节点的左子树仅包含键 小于 节点键的节点。
·节点的右子树仅包含键 大于 节点键的节点。
·左右子树也必须是二叉搜索树。
class Solution {
public:
int sum = 0;
TreeNode* convertBST(TreeNode* root) {
if (root != nullptr) {
convertBST(root->right);
sum += root->val;
root->val = sum;
convertBST(root->left);
}
return root;
}
};
为什么还是学不会呢?因为心里还有杂念!杂念完蛋去吧!