知识星球里这位录友二刷了「代码随想录」二叉树章节,总结了一篇自己的心得。
我感觉他总结的很好,值得大家伙们学习一下,每看完一章,自己能不能也总结来点东西。
毕竟我总结的,还是我自己的,大家自己总结出来的,才是自己的。
这位录友在二刷二叉树章节后,对我讲的很多细节,理解就深刻了很多,例如,他在总结里说的这些点:
- “一定要搞清楚遍历顺序不一定就是操作顺序”,
- “二叉树想成是有两个箭头的链表”
- “后序:当前节点要做的事情需要借助“左右子树”计算结果”
这些都是经常被大家忽略的,就是题目稀里糊涂的刷过去了,也不知道自己为什么就写对了,就是这样的感觉。
以下是这位录友分享在知识星球里的二叉树二刷总结:
二叉树串联其他知识点
不得不说二叉树真的是很重要的数据结构,它几乎可以把所有学过的这些算法串起来。
二叉树可以和数组,链表联系起来,用于二叉树的存储,比如说二叉树的前后中和层序遍历记录数值就可以用数组,lc114 二叉树展开成链表,这不就是链表的拼接问题,就是将current node的左子树拼到右子树上。
可以和字符串联系起来进行“子树的序列化和反序列化”,lc652 寻找重复的子树,如何判断两个子树是一样的呢,就可以将当前节点和它的子树序列化成字符串,用set和map进行存储和查找。
可以和哈希表联系起来,只要是将二叉树转换成其他数据结构表示(数组,字符串),用于查找和计数时,就会用到哈希。
可以和栈,队列联系起来。前中后序的迭代遍历就是用栈实现的,栈更像是“递归函数”的细节过程,在用递归遍历时,我们甚至只想当前节点如何操作就行。
而用栈我们需要脑中模拟第一次入栈,第二次入栈,再出栈等等,在节点出栈时候还要判断是否要进行操作,这不正是递归函数干的事情嘛。队列就是层序遍历。
对于队列,之前的优先队列,也就是堆,不就是一种用数组装二叉树的例子。
可以和双指针联系起来。lc116 填充每个节点的下一个右侧节点指针,就是给二叉树的节点创建一个next指针,用它指向右侧,定义两个指针node* nodepre
和node* node
,nodepre = nodepre->next
。
可以和回溯联系起来。回溯和递归是一种逻辑,可以说一个递归需要带上一个回溯,这是基于计算机入栈出栈的运作原理。
在A函数中调用B,A的参数可能会在B函数在运行时发生改变,为了保持A函数调用B之前和之后的一致性,必须在B函数运行完之后,将该参数重置到调用B之前的状态,这样B运行完出调用栈之后,A函数还能基于之前的运算结果继续运行。
可以和动态规划联系起来,动规和回溯最大的区别如果说在二叉树的层面看,就是:回溯是将二叉树全部遍历,或者说是按一定顺序遍历的,每条子树可能都走一遍,而动态规划是在进行二叉树搜索时候,每向下走一个节点就判断一下哪条路最优,最终得到的是一条子树路径。
二叉树的遍历
前中后序的遍历:一定要搞清楚遍历顺序不一定就是操作顺序,中序遍历记录二叉树就和遍历顺序不同。
其实遍历更像是指针的移动,二叉树想成是有两个箭头的链表(正常链表是一个指针,且指向下一个结点)。
从头部向尾部的指针移动和前序遍历很相似。
而指针先走到链表尾部,从尾部开始向头部逐个前移和后序遍历相似。
前序:顺序的解决问题。先将当前节点操作完,再处理子节点,等到子节点都处理完了,问题也就解决了。
后序:当前节点要做的事情需要借助“左右子树”计算结果,恰好后序就是等到左右子树递归函数完成后,可以记录他们的递归函数返回值,用于当前结点的操作。
例如计算二叉树的结点,重复子树的判断,公共祖先。
二叉树的构造
654 最大二叉树 数组的最大数是树的根节点。
先找最大数,再以最大数的index为分界,分成左右两个子数组,再进行左右的递归,这个解决思路正是前序递归遍历的思路。
105,106 前序 中序构造和中序 后序构造。
难点在于如何在递归函数中写数组的起始index和终点index,前序特点是起始位置是root,后序特点是最后的位置是root,他们是负责找到root,而中序的作用是以root为分界线,确定出左右两个子树,和654的想法有共通之处。
从上周到这周,有针对性挑了些二叉树和其他数据结构结合的题重刷,时间比预期稍长了一些,预计这周末可以二刷完二叉树,做最后总结。
我自己看了他的总结,发现了小小的问题,我在知识星球里也指了出来。
“而动态规划是在进行二叉树搜索时候,每向下走一个节点就判断一下哪条路最优,最终得到的是一条子树路径” 这一句说的有点问题。
动态规划其实也不是能直接判断哪一条是最优的,其性质也是需要遍历一遍,选出最优路径。
不过相对于回溯算法爆搜,回溯算法会重复计算子节点多次,而动态规划是用数组来记录每个节点的优值。
这一点,其实我在 动态规划:打家劫舍III里有讲过。
如果大家也能自己总结到这位录友的程度,说明对二叉树理解的确实到位了。