前沿:撰写博客的目的是为了再刷时回顾和进一步完善,其次才是以教为学,所以如果有些博客写的较简陋,是为了保持进度不得已而为之,还请大家多多见谅。
预:看到题目后的思路和实现的代码。
见:参考答案展示。
感思:对比答案后的思考,与之前做过的题目是否有关联。
行:
(1)对于没做出来的题目,阅读答案后重新做一遍;
(2)下次做题可以尝试改善的方向;
(3)有助于理解的相关的题目
通过默写题目思路来判断对每道题真实的掌握情况,只有了解客观的做题情况,才能有效地发现问题去解决。
题目回顾
1. 二叉树理论基础
1.1问题导向学习二叉树
(1)为什么要有二叉树?
便于查找搜索模拟树的结构?二叉树类似计算机0-1,效率高?也有图结构分别应用场景是什么?
图结构考虑到左右相邻的关系,而树结构更多的是父与子的关系,二叉树即至多只有两个孩子。
孩子的少,则数据量大时深度大,而二叉树的搜索能力类似归并算法,所以遍历效率是否会被影响?
二叉树类似“兄弟-孩子”的关系,一个连兄弟(有区别),一个连儿子(传承),这样仍有判断归类的功能。同时能最大限度的节省空间,而三个节点或更多子节点的结构则会相对消耗更多内存。
在实际应用中,平衡二叉树搜索较为稳定,相对于散列表,其是有序的,并且效率不一定比散列表慢散列表可能会出现哈希冲突,所以搜索效率也会受影响,并且解决的方式考虑不好会消耗更多地内存。
平衡二叉树即保证左右层差<2
(2)二叉树存储
链表(常使用)和数组(父节点数组下标是 i,它的左孩子 i * 2 + 1,右孩子 i * 2 + 2)
(3)二叉树遍历方式
二叉树遍历分为深度优先遍历(DFS)和广度优先遍历(BFS)。
- 深度优先遍历的实现方式:递归(也可用栈实现) and 迭代
- 深度优先遍历又分为前中后序遍历,其前中后都指的是中节点的位置,像深度前序优先遍历是按照中左右节点进行遍历。
- 广度优先遍历实现方法迭代,可使用队列实现。
面试手撕代码,所以数据结构的定义以及简单逻辑的代码一定要锻炼白纸写出来。
1.2二叉树相关概念:
(1)满二叉树
- 全部都子女双全;每层节点为0或2,总长度为2^k-1。
(2)完全二叉树
- 前面n层节点跟满二叉树一样,最后一层节点长度为1~2^(k-1),按照顺序从左到右增加。
(3)搜索二叉树
- 所有的左子树上的节点<根节点,所有的右子树上的节点>根节点,
- 要注意左子树的右子节点<根节点,同理右节点的左子节点>根节点。
(4)平衡二叉树
- 左右节点相差高度为1以内,其时间复杂度为logn可作为判断复杂度的指标;像set、map等采用平衡二叉树作为底层,而un_set\un_map则是哈希。
2. 递归遍历(必须掌握)
递归三元素实现递归的步骤:
- 确定递归函数的传入类型值和返回值
- 确定单层递归的逻辑
- 确定递归函数的终止条件
题目链接:144. 二叉树的前序遍历
前序遍历顺序:中左右
1.递归函数:无需返回值,传入(TreeNode root,List<Integer> result)
2.单层递归的逻辑:将当前节点值存入result里;递归函数传入左节点;递归函数传入右节点;
3.递归函数的终止条件:当前节点为空时,返回
题目链接:94. 二叉树的中序遍历
中序遍历顺序:左中右
1.递归函数与终止条件同上
2.单层递归的逻辑:递归函数传入左节点;将当前节点值存入result里;递归函数传入右节点;
题目链接:145. 二叉树的后序遍历
1.递归函数与终止条件同上
2.单层递归的逻辑:递归函数传入左节点;递归函数传入右节点;将当前节点值存入result里;
3.94. 二叉树的中序遍历(迭代)
题目链接:94. 二叉树的中序遍历
3.1二刷做不出原因分析
二刷知道要使用栈先进后出的特性实现,思考15分钟,没有想出来跳过
当时分析做不出来的原因:遍历左边后如何再回溯遍历右边节点?
思路:通过创建临时当前节点+出栈时把值赋予cur以此来遍历右节点。
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
//二叉树的迭代遍历;中序遍历:左中右:栈实现
List<Integer> result = new LinkedList<>();
if(root == null){
return result;
}
Deque<TreeNode> deque = new LinkedList<>();
TreeNode cur = root.left;
deque.push(root);
while(cur != null || !deque.isEmpty()){
if(cur != null){
deque.push(cur);
cur = cur.left;
}else{
cur = deque.peek();
deque.pop();
result.add(cur.val);
cur = cur.right;
}
}
return result;
}
}
前序遍历顺序:中左右
1.使用栈stack(先进后出)poppush存入当前节点,while循环终止条件为stack不为空;
2.TreeNode tNode = stack.pop();将栈弹出,并将弹出值赋予tNode临时变量。
3.将tNode值存入结果,并依次判断右左(弹出时则反过来)节点是否存在,若存在则存入栈。
4. 145. 二叉树的后序遍历(统一迭代)
题目链接:145. 二叉树的后序遍历
思路:统一迭代(标记)法
- 前序遍历顺序:中左右
- 与迭代遍历区别:通过空白作为遍历的输出信号
1步骤同上
2.TreeNode tNode = stack.peek();将栈弹出栈顶不出栈,并将值赋予tNode临时变量。
3.判断tNode是否为空:
- tNode不为空:
- stack.pop()弹出当前节点
- 依次判断右左(弹出时则反过来)节点是否存在,若存在则存入栈。
- 再依次存入当前节点和空白入栈。
- tNode为空:
- stack.pop()弹出当前节点(弹出的为空白节点)
- 并将此时的tNode值存入result中
- 最终再弹出当前节点(弹出的为存入结果节点)
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
//二叉树的统一迭代遍历:左右中
List<Integer> result = new LinkedList<>();
if(root == null){
return result;
}
Deque<TreeNode> deque = new LinkedList<>();
TreeNode node = new TreeNode();
deque.push(root);
while(!deque.isEmpty()){
if(deque.peek() != null){
node = deque.peek();
deque.push(null);
if(node.right != null){
deque.push(node.right);
}
if(node.left != null){
deque.push(node.left);
}
}else{
deque.pop();
//node = deque.pop();
result.add(deque.pop().val);
}
}
return result;
}
}
相关知识点:
stack.pop与stack.peek()区别:
- pop()出栈(先进后出)
- peek() 查看栈顶元素,但不出栈。
一刷时的总结
耗时一小时大体完成了二叉树一天的刷题总结
- 70%时间投入到看题默写思路上面,20%时间投入到对比答案上面。
若是六天的刷题量按照这种进度总结,则需要6小时才能完成。所以集中时间周日回顾是不现实的压力也大,但若不默写思路,则很难确定掌握情况。
行:
是否可以每天回顾昨日的题目,然后周末只需从整体进行总结。
周末对做题过程做总结复盘,结合一到六撰写的默写题总结,分别标注出哪些题目是重要已掌握、重要未掌握、不重要已掌握、不重要未掌握,为以后再次复习做好指南,分重点来攻克。