一、寻找二叉树最低公共祖先
【题目】:给定二叉树的两个节点node1和node2,找到它们的最低公共祖先节点。
方法一、使用HashMap记录每个节点的父节点
- 算法思路:首先进行一次遍历,将树中的全部节点都放入
HashMap
中,并记录它们对应的父节点,然后根据HashMap
求得node1所有的祖先节点,并将其放入一个Set
中,对node2同样向上查找,判断node2的每个祖先节点是否在Set
中,在的话则该节点就是最低公共祖先节点。
/**
* 使用HashMap记录每个节点的父节点
*/
public static Node lowestCommonAncestor1(Node head, Node node1, Node node2) {
Map<Node, Node> fatherMap = new HashMap<>();
// 记录每个节点的父节点
fatherMap.put(head, head);
process(head, fatherMap);
Set<Node> set1 = new HashSet<>();
Node cur = node1;
while (cur != fatherMap.get(cur)) {
set1.add(cur);
cur = fatherMap.get(cur);
}
set1.add(head);
cur = node2;
while (cur != fatherMap.get(cur)){
if (set1.contains(cur)) {
return cur;
}
cur = fatherMap.get(cur);
}
// 只有根节点是公共祖先
return head;
}
public static void process(Node head, Map<Node, Node> fatherMap) {
if (head == null) {
return;
}
fatherMap.put(head.left, head);
fatherMap.put(head.right, head);
process(head.left, fatherMap);
process(head.right, fatherMap);
}
方法二、递归优化
递归优化后的方法,比较抽象🤯,如何理解?
public static Node lca(Node head, Node node1, Node node2) {
if (head == null || head == node1 || head == node2) {
return head;
}
Node left = lca(head.left, node1, node2);
Node right = lca(head.right, node1, node2);
if (left != null && right != null) {
return head;
}
return left != null ? left : right;
}
- 我们要找node1和node2的最低公共祖先,则可能的情况有以下几种:
- 该公共祖先就是node1或者node2。
- 公共祖先不是node1或node2,需要从node1、node2向上找才能找到。
- 回到上面的代码,对于以head为根节点的子树,无非也就下面几种情况:
- head为node1/node2,则不管它下面的节点中有没有另一个node,head都认为自己 (node1/node2) 就是公共祖先,将自己返回🧐(可能自己不是公共祖先,这需要上面节点进行判断)。
- head不为node1/node2,则head需要根据左右两个子树的信息,来判断在它的下面是否有node1和node2的存在:
- 如果left和right都返回null,说明head下面没有node1和node2的存在,自己也向上返回null,并且认为在自己这棵树上不存在node1和node2的公共子节点;😔
- 如果left和right只有一个返回null,说明head下面的一个子树有node1/node2的存在(也有可能都存在),就把从该子树得到的节点返回,并且认为该节点就是node1和node2在自己这棵树上的最低公共祖先节点。😉
- 如果left和right都不返回null,说明head的左右子树分别有node1/node2的存在,此时很明显,head自己就是node1和node2的公共祖先节点,于是将自己向上返回。😋
- head为null,啥都没有,啥都不知道,直接返回。🤡
二、在二叉树中找到一个节点的后继节点
【题目】现在有一种新的二叉树节点类型如下:
public class Node{
public int value;
public Node left;
public Node right;
public Node parent;
public Node(int val) {
value = val;
}
}
- 该结构比普通二叉树节点结构多了一个指向父节点的parent指针。
- 假设有一棵Node类型的节点组成的二叉树,树中每个节点的parent指针都正确地指向自己的父节点,头节
点的parent指向null。 - 只给一个在二叉树中的某个节点node,请实现返回node的后继节点的函数。
- 在二叉树的中序遍历的序列中,node的 下一个节点叫作node的后继节点。
什么是后继节点?
以中序遍历的顺序,紧跟在节点A后面的节点B,称为节点A的后继节点
显然通过中序遍历,可以知道所有节点的后继节点,但题目中的Node结构给了父节点parent,要求某个节点的后继节点没必要把所有节点的后继节点都求出来。
- 算法思想:根据中序遍历的顺序,对于节点node,可以有以下两种情况:
- node有右子树,则node的后继节点就是,右子树的最左节点
- node无右子树,则需要依据node.parent不断向上看,如果node不是node.parent的左孩子,则继续往上看(node = node.parent),…,如果最终判定到了node是node.parent的左孩子,则node.parent就是要求的后继节点。如果最终判定到了node.parent = node,则要求的后继节点为null(此时,node为二叉树的最右节点)。
/**
* 在二叉树中找到一个节点的后继节点
*/
public static Node getSuccessorNode(Node node) {
if (node == null) {
return null;
}
if (node.right != null) {
return getLeftMost(node.right);
} else {
// 无右子树
Node parent = node.parent;
// 当前节点是其父亲节点的右孩子
while (parent != null && parent.left != node) {
node = parent;
parent = node.parent;
}
return parent;
}
}
public static Node getLeftMost(Node node) {
while (node.left != null) {
node = node.left;
}
return node;
}
三、二叉树的序列化和反序列化
什么是二叉树的序列化和反序列化?
就是内存里的一棵树如何变成字符串形式,又如何从字符串形式变成内存里的树。
方式之一就是利用先序遍历,将一棵树按照先序遍历序列进行拼接,同时各个节点直接用_
分割(随便什么都行),空节点用#
表示
/**
* 先序遍历顺序序列化树
*/
public static String serialByPre(Node root) {
if (root == null) {
return "#_";
}
String res = root.value + "_";
res += serialByPre(root.left);
res += serialByPre(root.right);
return res;
}
/**
* 先序遍历顺序进行树的反序列化
*/
public static Node reconByPre(String s) {
String[] value = s.split("_");
Queue<String> queue = new LinkedList<>(Arrays.asList(value));
return reconPreOrder(queue);
}
public static Node reconPreOrder(Queue<String> queue) {
String value = queue.poll();
assert value != null;
if (value.equals("#")) {
return null;
}
Node root = new Node(Integer.parseInt(value));
root.left = reconPreOrder(queue);
root.right = reconPreOrder(queue);
return root;
}
四、折纸问题
准备一较长的长方形纸条,拎着纸条的一头并让该头的某一面始终对着自己,开始第一次对折,在纸条中心出现一条凹折痕;第二次对折,纸条上下分别出现了凹折痕和凸折痕,再次对折·······,当对折次数达到N时,从上到下打印所有折痕。
不难发现规律:N次对折时,会在N-1次对折折痕的上下两侧出现凹折痕和凸折痕,所以可将所有折痕看作是一完全二叉树的节点。
public static void printAllFolds(int n) {
process(1, n, true);
}
/**
* 递归过程,来到了某一个节点
*
* @param i 当前节点的层数
* @param n 总层数,即一共折了多少次
* @param down 当前节点是凹节点还是凸节点,true为凹,false为凸。
*/
public static void process(int i, int n, Boolean down) {
if (i > n) {
return;
}
process(i + 1, n, true);
System.out.println(down ? "凹" : "凸");
process(i + 1, n, false);
}