二叉树:
这种数据结构,我必须要搞明白的点:
1:二叉树的深度 对应着 二叉树的前序遍历(中->左->右)的思路(因为深度是从根节点->叶子节点来算的)
对应的经典题目是:T110 平衡二叉树
2:二叉树的高度 对应着 二叉树的后序遍历(左->右->中)的思路(因为高度是从叶子节点->根节点来算的)
对应的经典题目是:T110 平衡二叉树
3:树的前(中左右)、中(左中右)、后(左右中)序遍历之统一迭代法(stack)以及递归法
统一法do二叉树前序遍历(为例子)
class Solution{
public://统一迭代法do前序遍历
vector<int> preorderTraversal(TreeNode* root) {
vector<int> res;
if (root == nullptr) return res;
stack<TreeNode*> stk;
stk.push(root);
while (!stk.empty()) {
TreeNode* tnode = stk.top();
if (tnode != nullptr) {
stk.pop();
//再分别按照中左右的方式来倒序放元素进入的到stack中
if (tnode->right) stk.push(tnode->right);//先让右子节点do压栈,因为先进后出
//秉承着先进后出这种数据结构的原则,让right子树先push进去!
if (tnode->left) stk.push(tnode->left);//后让左子节点do压栈,因为后进先出
stk.push(tnode);//最后让中节点和空节点标记do压栈操作
stk.push(nullptr);
//输出按中左右的顺序 与我代码写的方向刚好相反
}
else {
stk.pop();//pop nullptr 这个空指针(节点)标记!
tnode = stk.top();
stk.pop();
res.push_back(tnode->val);
}
}
return res;
}
};
递归版本的二叉树前序遍历
class Solution {
public:
void preOrder(TreeNode* cur,vector<int>& res){//1递归函参及返回值的sure
//2递归终止条件
if(cur == nullptr) return;//空 直接end
//3单层的递归逻辑
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;
}
};
对应的经典题目是:
注意:二叉树中的前中后、层序遍历算法,其实就是图论中的DFS、BFS的应用!
前中后 序遍历算法 对应着 DFS(Depth First Search Algorithm)深度优先搜索算法 的思想
层 序遍历算法 对应着 BFS(Boundary First Search Algorithm)广度优先搜索算法 的思想
注意:写递归函数时,有个技巧!即递归三要素法!
①---递归函数的形参表以及返回值
②---终止递归函数的处理条件
③---递归函数的单层处理logic
只要掌握这个三要素法,以后所有递归函数就按照这个套路来写即可!套着模板玩儿~
总结:
二叉树的迭代法:
①---用栈stack模拟深度遍历(统一迭代法)
(注:统一迭代法在处理具体题目logic时,一般都是在tnode == nullptr时才处理的!因为只有此时才是真正遍历到每个节点且能够对该节点进行具体逻辑代码处理的时候!)
②---用队列queue模拟广度遍历(层序遍历法)
二叉树的递归法(递归三部曲~):
①---前(中左右)序遍历
②---中(左中右)序遍历
③---后(左右中)序遍历
4:树的层序遍历(queue迭代法)
层序遍历 == 广度优先BFS bound first search
前中后序遍历(不论是迭代法还是递归法!) == 深度优先遍历DFS depth first search
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
if(root == nullptr)return {};
vector<vector<int>> res;
queue<TreeNode*> Que;
Que.push(root);
while(!Que.empty()){
int Size = Que.size();//记录本次循环时queue的固定大小
vector<int> tmpVec;//记录临时这一层二叉树的节点value
tmpVec.reserve(Size);//预留合适的空间保存临时节点之value
for(int i=0;i<Size;++i){
auto tnode = Que.front();
Que.pop();
tmpVec.push_back(tnode->val);
if(tnode->left)Que.push(tnode->left);
if(tnode->right)Que.push(tnode->right);
}
res.push_back(tmpVec);
}
return res;
}
};
对应的经典题目是:102.二叉树的层序遍历
5:递归的时候其实隐藏了回溯(且需要记住!有一次递归,就必须对应要有一次回溯!)
对应的经典题目是:257. 二叉树的所有路径
6:写关于二叉树的递归函数时,什么时候需要返回值,什么时候不需要返回值,这一点很重要!
对应的经典题目是: 112. 路径总和 和 113. 路径总和ii
ans:
当递归函数需要遍历二叉树的所有节点才能完成题目的要求时,此时没有返回值,即返回值为void。
当不需要遍历二叉树所有节点,遇到符合条件的节点就完成题目要求时,此时就需要返回值(但具体返回值是何种类型就具体情况具体分析即可~)
还有一种需要返回值的case就,需要构造新的二叉树时,就必须要让返回值为TreeNode*了!
7:关于构造二叉树,要学会的是:如何从前序+中序arr构造出原唯一的二叉树,和,从中序+后序arr构造出原唯一的二叉树,必须要会这种套路!本质上构造二叉树的套路就是个递归而已。(构造二叉树的题目思路,再不济你背下来也能搞clear~)
当然,如果没有中序arr的话,就无法构造出唯一的一棵二叉树了。
比如:res中这2棵树的前序arr和后序arr是相同的!因此,若只给定前序和后序arr则无法构造唯一的一棵二叉树!
对应的经典题目是:(这3个题目刷完基本上会灵活运用构造二叉树相关的logic了)
654.最大二叉树(这个题目我自认为自己写的答案的logic非常好~与上述两个题目的logic属于是承上启下的作用~)
8:合并二叉树的操作,必须要把合并的logic搞clear!递归logic是比较清晰的,难的是迭代法的logic处理!
对应的经典题目是:617.合并二叉树
//way1:
class Solution {
public:
//这里仍然以修改root1的树结构作为函数的返回值!当然,你也可以通过修改root2的树结构来作为返回值!
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
//处理特殊case
if(root1 == nullptr)return root2;
if(root2 == nullptr)return root1;
//这里用前中后序遍历思路都ok,这里我以中序遍历思路来do递归!
//新节点->val == 左右两棵树节点val之和!
root1->val += root2->val;//中
//root1->left与root2->left合并后作为新的root1->left
root1->left = mergeTrees(root1->left,root2->left);//左
//root1->right与root2->right合并后作为新的root1->right
root1->right = mergeTrees(root1->right,root2->right);//右
return root1;//返回整棵树的根节点
}
};
//way2:
class Solution {
public:
//这里仍然以修改root1的树结构作为函数的返回值!当然,你也可以通过修改root2的树结构来作为返回值!
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
//处理特殊case
if(root1 == nullptr)return root2;
if(root2 == nullptr)return root1;
//这里用前中后序遍历思路都ok,这里我以中序遍历思路来do递归!
TreeNode* newRoot = new TreeNode(0);
//新节点->val == 左右两棵树节点val之和!
newRoot->val = root1->val + root2->val;//中
//root1->left与root2->left合并后作为新的newRoot->left
newRoot->left = mergeTrees(root1->left,root2->left);//左
//root1->right与root2->right合并后作为新的newRoot->right
newRoot->right = mergeTrees(root1->right,root2->right);//右
return newRoot;//返回整棵树的根节点
}
};
//way3:
class Solution {
public:
//这里以修改root1的树结构作为函数的返回值!当然,你也可以通过修改root2的树结构来作为返回值!
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
if(root1 == nullptr)return root2;
if(root2 == nullptr)return root1;
//用层序列遍历(queue)的方法来合并两颗二叉树!
queue<TreeNode*> que;
que.push(root1);
que.push(root2);
while(!que.empty()){
//拿到2个非空的node
auto tnode1 = que.front();que.pop();
auto tnode2 = que.front();que.pop();
//考虑所有case来判断合并两棵二叉树的logic
tnode1->val += tnode2->val; //既然能进来queue队列中了,那么就意味着当前的tnode1和tnode2都非空!
//root1->left空 root2->left空 不do事情
//root1->left空 root2->left不空 让root1->left 指向 root2->left
//root1->left不空 root2->left不空 把他们都push进到queue队列中去!继续迭代!
//root1->left不空 root2->left空 不do事情
//root1->right空 root2->right空 不do事情
//root1->right空 root2->right不空 让root1->right 指向 root2->right
//root1->right不空 root2->right不空 把他们都push进到queue队列中去!继续迭代!
//root1->right不空 root2->right空 不do事情
if(tnode1->left != nullptr && tnode2->left != nullptr){
que.push(tnode1->left);que.push(tnode2->left);
}
if(tnode1->left == nullptr && tnode2->left != nullptr){
tnode1->left = tnode2->left;
}
if(tnode1->right != nullptr && tnode2->right != nullptr){
que.push(tnode1->right);que.push(tnode2->right);
}
if(tnode1->right == nullptr && tnode2->right != nullptr){
tnode1->right = tnode2->right;
}
//结束本轮迭代的logic处理了!next term!~
}
return root1;
}
};
//way4:
class Solution {
public:
//这里以修改root2的树结构作为函数的返回值!当然,你也可以通过修改root1的树结构来作为返回值!
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
if(root1 == nullptr)return root2;
if(root2 == nullptr)return root1;
//用层序列遍历(queue)的方法来合并两颗二叉树!
queue<TreeNode*> que;
que.push(root2);
que.push(root1);
while(!que.empty()){
//拿到2个非空的node
auto tnode2 = que.front();que.pop();
auto tnode1 = que.front();que.pop();
//考虑所有case来判断合并两棵二叉树的logic
tnode2->val += tnode1->val; //既然能进来queue队列中了,那么就意味着当前的tnode1和tnode2都非空!
//root2->left空 root1->left空 不do事情
//root2->left空 root1->left不空 让root2->left 指向 root1->left
//root2->left不空 root1->left不空 把他们都push进到queue队列中去!继续迭代!
//root2->left不空 root1->left空 不do事情
//root2->right空 root1->right空 不do事情
//root2->right空 root1->right不空 让root2->right 指向 root1->right
//root2->right不空 root1->right不空 把他们都push进到queue队列中去!继续迭代!
//root2->right不空 root1->right空 不do事情
if(tnode2->left != nullptr && tnode1->left != nullptr){
que.push(tnode2->left);que.push(tnode1->left);
}
if(tnode2->left == nullptr && tnode1->left != nullptr){
tnode2->left = tnode1->left;
}
if(tnode2->right != nullptr && tnode1->right != nullptr){
que.push(tnode2->right);que.push(tnode1->right);
}
if(tnode2->right == nullptr && tnode1->right != nullptr){
tnode2->right = tnode1->right;
}
//结束本轮迭代的logic处理了!next term!~
}
return root2;
}
};
9:判断一棵树是否为BST时,你必须要反应出的唯一一个Key点(这是判断BST的关键所在)是:
Key:if一棵树的中序遍历输出序列,是一个升序(从小到大)的序列 <=>这棵树一定是BST(Binary Search Tree二叉搜索树)!
对应的经典题目是:98.验证二叉搜索树