leetcode 1257
- java的ACM模式读取数据
- 遍历 + hashmap辅助建立多叉树(一边遍历一边更新上下文)
- 多叉树的dfs
- 多叉树的LCA问题
换汤不换药 T235 二叉树的LCA
T236 二叉搜索树的LCA
本题: 多叉树的LCA
思路
- 多叉树的递归模版(dfs配合for循环)
- 管多个children要结果,如果只有一个非空的child返回值,返回该值,如果有两个,返回自己。证明: 不可能出现 > 2个非空子过程返回结果。
case
- 如果见到root为p或q之一,为什么不需要继续递归,也能保证结果正确
证明: 如果见到p或者q之一为root本身,那么另一个node要么位于递归的另一半(root的兄弟,或者root的非直系长辈),要么另一个node就在当前root的子过程中。对于后者明显成立,因为这种情况下LCA(p, q)就是p或者q。对于前者,则是我们递归的通用流程
- 返回root的另一个条件是,p和q 都出现在当前root的children里。这个很好理解。我们来看看"如果子过程仅有一个返回结果,则返回该结果"的含义。
我们知道,如果只有一个子过程有返回值,其他都为null,此时意味着只在这一个子过程上找到了结果。我们要理解这个结果意味着 要么是p在这个子过程上,要么是q在这个子过程上,要么这个结果就是p和q的LCA,所以这个结果要么是未来有用,要么是已经计算完的结果了,无论如何传上去就行。
实际上,递归就是一个"面向结果"然后凑过程的东西,我们假设递归就能返回这样的结果,也就是递归的返回值就意味着要么是p在这个子过程上,要么是q在这个子过程上,要么这个结果就是p和q的LCA(没错,就是先假设他会这么返回,然后再去凑他为什么会这么返回,其实就是递推式)。
import java.io.*;
import java.util.*;
public class leetcode1257 {
// 多叉树
public static class TreeNode {
String val; // region name
public TreeNode[] children; // java不存在所谓 "incomplete type"
public TreeNode(String val) {
this.val = val;
this.children = new TreeNode[0];
}
}
public static void main(String[] args) {
// 最小公共区域
FastReader in = new FastReader();
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
int lines = in.nextInt();
String[][] ss = new String[lines][];
for (int i = 0; i < lines; i++) {
String[] cities = in.nextLine().split(",");
ss[i] = cities;
}
for (String[] s : ss) {
System.out.println(Arrays.toString(s));
}
String r1 = in.nextLine();
String r2 = in.nextLine();
System.out.println(r1);
System.out.println(r2);
// 1. 把String[][]组织为树形结构
// 这明显是一个多叉树
// 定义多叉树结构,遍历 + hashmap辅助建树
TreeNode root = null;
// hashmap 用于跟踪上下文的node
Map<String, TreeNode> tm = new HashMap<>();
for (int i = 0; i < ss.length; i++) {
String[] s = ss[i];
// 如果已经在map里存在,则不新建
TreeNode rt = tm.getOrDefault(s[0], null) == null ? new TreeNode(s[0]) : tm.get(s[0]);
tm.put(s[0], rt);
if (i == 0) {
root = rt;
}
TreeNode[] children = new TreeNode[s.length - 1];
for (int j = 1; j < s.length; j++) {
TreeNode nd = new TreeNode(s[j]);
tm.put(s[j], nd);
children[j-1] = nd;
}
rt.children = children;
}
// dfs(root);
System.out.println(LCA(root, tm.get(r1), tm.get(r2)).val);
}
static List<TreeNode> path = new ArrayList<>();
public static TreeNode LCA(TreeNode rt, TreeNode r1, TreeNode r2) {
// 多叉树公共祖先
// 1. 如果rt就是r1或r2,直接返回
// 此时不需要继续向下递归,因为此时要么另一个node在当前node 的子过程里,此时结果就是当前node
// 要么另一个node位于子树的另一部分,将由另一半递归返回结果
if (rt == r1 || rt == r2) {
return rt;
}
// 否则 遍历所有孩子
int retNum = 0;
TreeNode rootRet = null;
/*
如果有两个child返回非空,则表示root为 r1和r2的祖先
root
/ ... | ... \
child1(include q) ....... childn(include p)
如果child_i存在q,child_j存在p,那p和q的最近祖先肯定是root。
那么对于root的父过程,肯定只有root这一个孩子给他返回结果,因为p和q都在 root里,就不可能在他的兄弟上。
因此,如果只有一个child_i返回非空,那么就直接返回这个非空的结果
如果所有孩子都不是非空,则说明整个树都没有合法的结点
*/
for (TreeNode child : rt.children) {
// 我们知道,**最多只有两个** child会返回非空的结果
// 因为我们只寻找两个node,也就是r1和r2,两个node不可能出现在3棵树里。
// 相反,可能当前root的所有子树里都没有r1和r2,此时就返回null,若只有一个子树有,就返回有结果的那个结果
// 如果两个子树存在结果,则返回自己。
TreeNode ret = LCA(child, r1, r2);
if (ret != null) {
if (retNum == 0) {
// 如果此时自己是第一个
rootRet = ret;
} else {
// 此时p和q分别位于root的两个子孩子上, 返回root为LCA
rootRet = rt;
}
retNum++;
}
}
// 遍历完所有的结点后,将结果返回父过程
return rootRet;
}
public static void dfs(TreeNode rt) {
if (rt == null) {
return;
}
// 多叉树的递归,for循环退出 = 对root为null判断
// for循环这个代码块代表以rt为结点的所有孩子,for循环的child之间彼此为兄弟关系
// 应该避免兄弟之间互相影响,所以选择回溯法
path.add(rt);
for (TreeNode child : rt.children) {
dfs(child);
}
System.out.println("此时的dfs路径:");
for (TreeNode nd : path) {
System.out.printf(nd.val + " ");
}
System.out.println("");
path.remove(rt);
}
}
class FastReader {
// 简单理解就是一个词法分析器
StringTokenizer st;
// 简单理解就是一个带缓冲的文件句柄
BufferedReader br;
public FastReader() {
br = new BufferedReader(new InputStreamReader(System.in));
}
String next() {
while (st == null || !st.hasMoreElements()) {
try {
st = new StringTokenizer(br.readLine());
} catch (IOException e) {
e.printStackTrace();
}
}
return st.nextToken();
}
int nextInt() {
return Integer.parseInt(next());
}
long nextLong() {
return Long.parseLong(next());
}
double nextDouble() {
return Double.parseDouble(next());
}
String nextLine() {
String str = "";
try {
str = br.readLine();
} catch (IOException e) {
e.printStackTrace();
}
return str;
}
}