ARTS:Algorithm、Review、Tip、Share
- Algorithm 算法题
- Review 英文文章
- Tip 回想一下本周工作中学到的一个小技巧
- Share思考一个技术观点、社会热点、一个产品或是一个困惑
Algorithm
二叉树的递归序遍历
递归序
二叉树的遍历有:先序、中序、后续三种主要方式,主要区别在任何子树的处理顺序上,具体如下。
- 先序:先处理头节点、再左子树、然后右子树;
- 中序:先处理左子树、再头节点、然后右子树;
- 后续:先处理左子树、再右子树、然后头节点。
虽然比较简单,但记忆起来还是比较麻烦的!实际上,我们可以通过递归的方法实现如上三种遍历形式,其实递归不仅可以作为其实现方式,更是三种遍历方式的本质!
先序、中序、后续,都是递归序加工的结果。
public static void f(Node head) {
if (head == null) {
return;
}
// 1
f(head.left);
// 2
f(head.right);
// 3
}
方法 f 的实现中包含了另外两次递归调用,分别对应左子树、右子树的处理,也意味着我们在处理每个节点的时候,都有三次处理的机会,一次是在刚进入这个节点的时候(即位置1)、二次是在执行完左子树 f(head.left) 的处理返回后的时机(即位置2)、三次是在执行完右子树 f(head.right) 的处理返回后(即位置3)。 我们将处理当前节点的过程分别放在1、2、3的位置上时,分别对应了先序、中序、后续的实现!
练习:94. 二叉树的中序遍历
给定一个二叉树的根节点 root ,返回它的 中序 遍历。
示例 1:
输入:root = [1,null,2,3] 输出:[1,3,2] 示例 2:
输入:root = [] 输出:[] 示例 3:
输入:root = [1] 输出:[1]
示例 4:
输入:root = [1,2] 输出:[2,1] 示例 5:
输入:root = [1,null,2] 输出:[1,2]
提示:
树中节点数目在范围 [0, 100] 内 -100 <= Node.val <= 100
进阶: 递归算法很简单,你可以通过迭代算法完成吗?
来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/bi…
递归解法
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> out = new LinkedList<>();
in(root, out);
return out;
}
private void in (TreeNode node, List<Integer> out) {
if (node == null) {
return;
}
this.in(node.left, out);
out.add(node.val);
this.in(node.right, out);
}
非递归序
可以明确的是,通过自己使用栈结构的方式,可以将任何递归函数改写成非递归的方式,具体到二叉树的先、中、后续遍历来看,具体实现如下:
先序遍历
先序的实现是:头、左、右的顺序,我们在入栈时,需将右子节点先入栈、而后是左子节点,这样弹出打印的时候也就是要求的顺序了:
public static void pre(Node cur) {
System.out.print("pre order: ");
Stack<Node> stack = new Stack<>();
stack.push(cur);
while (!stack.isEmpty()) {
// 1. 弹出并打印
cur = stack.pop();
System.out.print(cur.value + " ");
// 2. 如有右孩子,压入栈
if (cur.right != null) {
stack.push(cur.right);
}
// 3. 如有左孩子,压入栈
if (cur.left != null) {
stack.push(cur.left);
}
}
System.out.println();
}
中序遍历
我们需将整个左边界上的节点全部入栈,然后到达最左子结点,弹出并打印处理后,进入当前节点的右子树上,继续执行左边界入栈操作;没有右子树,则弹出栈中元素,执行上一级。
public static void in(Node cur) {
System.out.print("in order: ");
Stack<Node> stack = new Stack<>();
while (!stack.isEmpty() || cur != null) {
// 左边界入栈
if (cur != null) {
stack.push(cur);
cur = cur.left;
} else {
// 弹出并打印处理,定位到右子树(右子树为空,则弹出栈中元素为上一级)
cur = stack.pop();
System.out.print(cur.value + " ");
cur = cur.right;
}
}
System.out.println();
}
后续遍历-1
与先序遍历相反,这次按照头结点、左子节点、右子节点的顺序压栈处理(s1栈),弹出时借助另外一个栈(s2)来实现顺序的转换,比如只有三个节点(root 下面跟了左子节点 left、右子节点 right)的时候,进入s1栈的顺序为:
- root 压入 s1;
- s1 弹出 root,root 压入s2;
- s1 压入 left;
- s1 压入 right;
- s1 弹出 right,right 压入s2;
- s1 弹出 left,left 压入s2;
此时s2中,right在底、right在中间、left在最上,依次弹出处理即实现后续遍历
public static void pos1(Node cur) {
System.out.print("post1 order: ");
Stack<Node> s1 = new Stack<>();
Stack<Node> s2 = new Stack<>();
s1.push(cur);
while (!s1.isEmpty()) {
cur = s1.pop();
s2.push(cur);
if (cur.left != null) {
s1.push(cur.left);
}
if (cur.right != null) {
s1.push(cur.right);
}
}
while (!s2.isEmpty()) {
System.out.print(s2.pop().value + " ");
}
System.out.println();
}
后续遍历-2
如果不借助另外一个栈结构,如何实现后续遍历? 使用两个变量:
- c 表示当前要处理的栈顶元素,首先定位到最左节点,左边界入栈;
- h 表示上一个处理过的节点,最开始在一个不干扰处理逻辑分支1的位置。
处理逻辑:
- 判断 c 的左子节点是否处理过 (h 不为 c.left, 且不为 c.right,则表示没处理过,将c.left 入栈)
- 判断 c 的右子节点是否处理过 (h 不为 c.right,则表示没处理过,将c.right 入栈)
左边界入栈完成后,弹出栈顶元素、并处理,此时用h标记一下当前位置,然后进入下一轮循环; c仍然取栈顶元素、判断与h(上一次处理元素)的关系,总会先处理左节点、然后因为h变为c的左子,后面会压入右子节点,右子节点完成后,才会定位到上一级,实现后续遍历的功能。
具体代码如下:
public static void pos2(Node cur) {
if (cur == null) {
return;
}
System.out.print("post2 order: ");
Stack<Node> stack = new Stack<>();
Node h = cur;
Node c;
stack.push(cur);
while (!stack.isEmpty()) {
c = stack.peek();
if (c.left != null && c.left != h && c.right != h) {
stack.push(c.left);
} else if (c.right != null && c.right != h) {
stack.push(c.right);
} else {
h = stack.pop();
System.out.print(h.value + " ");
}
}
System.out.println();
}
练习:非递归序实现leetcode94
在知道了如何通过非递归方式实现的方法后,我们可以在94题递归解法的基础上,更近一步:
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> out = new LinkedList<>();
Stack<TreeNode> stack = new Stack<>();
while (!stack.isEmpty() || root != null) {
if (root != null) {
stack.push(root);
root = root.left;
} else {
root = stack.pop();
out.add(root.val);
root = root.right;
}
}
return out;
}
Review
本周对 Guava-wiki 有了初步的学习,先看了Basic utilities、Collections中开头部分,对Guava的使用其实之前都是简单了解,这次希望能有一个详细的认识,熟悉好相关的工具,在实际工作中才能更有效的使用。
初步总结,见思维导图,后续会逐步完善。
Tip
在学习Guava的同时,第二遍看《EffectiveJava》这本书,上一次阅读没有好好记录笔记、总结,需要再详细看一遍,目前看完了第一章的内容。
Share
2022年1月27日:吴军老师、刘润老师视频号交流笔记,权且放在这里分享一下:
这次交流涉及三个大的主题:科技、商业、孩子教育,仅对部分印象深刻的内容整理。
-
科学是把钱变成知识,技术是把知识变成钱。
-
了解孩子的心智发育、发展 不同孩子,孩子的心理学,他能理解什么内容、认知水平随着年龄不同而不同。 小学一年级左右:同理心还不健全 11/12岁时,才会有抽象思维能力、归纳--举一反三
-
每个孩子都有自己的天赋 天赋三原色(三要素): 抽象 形象 语言
长期发展:三个基础智力元素上的培养