[英]A variation of the “Find common ancestor”
我最近接受了电话采访。它将问题编码作为整个过程的一部分。
问题是Find the most closest common ancestor of a tree
但有一个扭曲的变化 。 树很像图形,即可以连接子节点。 例:
A
/
B
| \
C E
| |
D F
\ /
G
在这种情况下,给定该树以及节点F
和D
,得到的最接近的共同祖先将是B
第二个转折是树呈现为阵列。 实现方法有以下输入:
public String getCA(String[] nodes, String[][] parentNodes, String targetNode1, String targetNode2)
在这个例子中, nodes
= {"G", "F", "E", "D", "C", "B", "A"}
和parentNodes
= {{"F","D"},{"E"}, {"B"}, {"C"}, {"B"}, {"A"}, null}
基本上对于nodes[i]
,父nodes[i]
是parentNodes[i]
。
说实话,我完全惊慌失措(已经非常紧张)并且花了很长时间才找到答案。
虽然我认为这是递归求解的,但我想出了一个迭代解决方案,据我所知可行:我在队列中推送节点,然后首先上升第一个目标节点然后是第二个节点。 一旦我找到一个已经遇到的节点,我认为它是解决方案(添加了注释以清除事物)。
public String getCA(String[] nodes, String[][] parentNodes, String targetNode1, String targetNode2) {
if(nodes == null || parentNodes == null){
throw new IllegalArgumentException();
}
Map<String, String[]> map = new HashMap<String, String[]>();
for(int i = 0; i < nodes.length; i++){
map.put(nodes[i], parentNodes[i]);
}
//These are the parents visited as we go up
Set<String> parentsSeen = new HashSet<String>();
parentsSeen.add(targetNode1);
Queue<String> path = new LinkedList<String>();
String[] parents = map.get(targetNode1);
//The root is the common parent
if(parents == null){
return targetNode1;
}
//Build the path up
for(String parent:parents){
path.add(parent);
}
while(!path.isEmpty()){
String currentParent = path.remove();
parentsSeen.add(currentParent);
parents = map.get(currentParent);
if(parents == null){
continue;
}
for(String parent:parents){
path.add(parent);
}
}
parents = map.get(targetNode2);
//It is the root, so it is the common parent
if(parents == null){
return targetNode2;
}
//Start going up for the targetNode2. The first parent that we have already visited is the common parent
for(String parent:parents){
if(parentsSeen.contains(parent)){
return parent;
}
path.add(parent);
}
while(!path.isEmpty()){
String currentParent = path.remove();
if(parentsSeen.contains(currentParent)){
return currentParent;
}
parents = map.get(currentParent);
if(parents == null){
continue;
}
for(String parent:parents){
path.add(parent);
}
}
return null;
}
我没有得到前进的电话。 现在由于我“自学成才”,我有兴趣了解我是如何搞砸到这里的。 由于这是技术问题,我认为这不是主观问题,希望我能从经验丰富的人那里得到帮助。
因此,作为同事程序员,您将如何处理该问题?您如何评估我的解决方案? 我需要做些什么来提高我的技能?
你可以尽可能地直截了当。 只要我能理解出了什么问题并且学会了我就会感到满意
我甚至不清楚“最接近”是什么意思。 请考虑以下图表:
I
/\
/ \
/ \
H E
|\ /|
| \ / |
G \/ D
| /\ |
| / F C
|/ \|
A B
这里有2个共同的祖先A和B,H和E. H是A和B的距离2.E是距离A的距离1但距离B的距离是3.我选择哪个?
此外,无论你对这个问题的答案是什么,从一个人那里找到一组祖先,然后从另一个人那里做一个BFS也行不通。 找到A的所有祖先然后从B做BFS首先找到H,找到B的所有祖先然后从A做BFS首先找到E. 作为对手,我可以切换A和B,使你的算法失败,无论你做出什么选择(选择2/2还是1/3更好)。
因此,正确的算法必须比祖先集计算加上BFS更复杂。 除非你告诉我如何做出这个选择,否则我不确定我是否可以确定正确的算法。
非常好的问题,你真的努力写这个。 对于这次采访我很抱歉,我知道当这样的事情发生时一定非常令人沮丧。 让我们看看问题所在。
想到的一个想法是:您可以递归向上,直到从两个目标节点到达根并构建两个列表。 最后,只需在两个列表中找到第一个公共节点。
public static List<String> visitUpwards(Map<String, String[]> mapping, String current) {
List<String> list = new ArrayList<String>();
list.add(current);
if(mapping.get(current) == null) return list;
else {
for(String s: mapping.get(current)) {
list.addAll(visitUpwards(mapping, s));
}
return list;
}
}
编辑 :第二个想法,这不是一个好主意,因为它基本上向上进行DFS搜索,使得找到第一个共同的祖先很困难。 更好的想法是为每个目标运行一个BFS(这可能看起来与您的解决方案类似:D):
public static Set<String> BFS(Map<String, String[]> mapping, String node) {
Queue<String> queue = new LinkedList<String>();
Set<String> result = new LinkedHashSet<String>();
queue.add(node);
while(!queue.isEmpty()) {
String current = queue.remove();
result.add(current);
if(mapping.get(current) != null)
for(String s: mapping.get(current)) {
queue.add(s);
}
}
return result;
}
然后使用另一张地图找到第一个共同的祖先:
Set<String> list1 = BFS(mapping, "D");
Set<String> list2 = BFS(mapping, "G");
System.out.println(list1);
System.out.println(list2);
Map<String, Boolean> src = new HashMap<String, Boolean>();
for(String s: list1) {
src.put(s, true);
}
for(String s: list2) {
if(src.get(s) != null && src.get(s)) {
System.out.println(s);
break;
}
}
我不能告诉你为什么公司没有给你回电话。 但是,通过将代码提取到更小,更有意义的方法中,而不是使用具有所有详细的低级逻辑的单个大方法,您的代码将受益匪浅。 虽然我还没有尝试编译代码并在某些测试用例上运行它,但是从快速阅读开始,我完全不相信代码是正确的。 最近的评论祖先可能是targetNode1
或 targetNode2
(例如,如果targetNode2
是targetNode2
的父targetNode1
)。 (我想这取决于你如何定义“最接近的共同祖先”的细节 - 你应该澄清它在这种情况下意味着什么。)
targetNode1
和targetNode2
上使用相同的方法。 更优化的解决方案可以是使用一系列布尔值来进行簿记。 每个节点一个。 将它们全部归为假 。 访问节点时将它们设置为true 。 正如你今天所做的那样,向上移动“树”。 一旦你来到一个已经访问过的节点(在数组中设置为true ),你就找到了共同的祖先。
编辑:更好的方法是为每个原始节点提供一个具有唯一标识符的整数数组。 如果有循环,我们需要知道谁已经访问过该节点,它可能就是我们自己。
考虑以下图表的变体......
A
|
X---B---Y
\ / \ /
C E
| |
D F
\ /
G
nodes
= {"G", "F", "E", "D", "C", "B", "A", "X", "Y"}
parentNodes
= {{"F","D"},{"E"}, {"Y","B"}, {"C"}, {"X","B"}, {"A"}, null, {"B"}, {"B"}}
我认为你的程序在遇到节点"C"
时会遇到困难,因为它有两个要探索的父节点。 您需要使用递归算法或使用迭代算法为未探测的节点管理某种堆栈。
既然你似乎对iteraton有偏见,你可以尝试类似的东西:
创建一个数组(或类似的数据结构)。 每个数组元素包含三条信息:
nodeName: string - name of a node from your `nodes` string
pathLength[2]: integer - array of minimum path length from refrence node (1 or 2).
创建堆栈。 每个堆栈元素包含三条信息:
nodeName: string - name of a node to "explore"
lengthToHere: integer - length of path from reference node to `nodeName`
pathNumber: integer - 1 for first reference node, 2 for second.
算法:
Initialize array with nodeName from nodes and set each pathLength to "infinity"
Push both starting reference nodes onto the stack:
push("F", 0, 1)
push "D", 0, 2)
Repeat until stack is empty:
- pop(nodeName, lengthToHere, pathNumber)
- update pathLength[pathNumber] of element having nodeName in array with minimum of
its current value and lengthToHere
- for each parent of nodeName push(parent, lengthToHere + 1, pathNumber)
一旦评估了来自起始参考节点的所有路径,堆栈就为空。 如果存在共同的祖先,则给定nodeName的pathLength值都将小于无穷大。 将这些值一起添加到这个共同祖先的总路径长度。 报告具有最小总和的nodeName。
对于给定的目标节点x
和y
请执行以下操作:
x
及其所有祖先添加到集合S
。 y
是否在S
,如果没有,则检查y
的父母是否在S
,如果不在,则检查他们的父母是否在S
,依此类推。 运行时间为O(n)。
我可能误解了一些东西,但我认为这部分算法可能会浪费大量时间来研究节点:
while(!path.isEmpty()){
String currentParent = path.remove();
parentsSeen.add(currentParent);
parents = map.get(currentParent);
if(parents == null){
continue;
}
for(String parent:parents){
path.add(parent);
}
}
这看起来像树的广度优先搜索,但由于孩子有多个父母,您最终可能会多次搜索相同的节点。
您可以通过添加对您父母的集合的检查来停止浪费的时间:
while(!path.isEmpty()){
String currentParent = path.remove();
if (parentsSeen.contains(currentParent)) {
continue;
}
parentsSeen.add(currentParent);
parents = map.get(currentParent);
if(parents == null){
continue;
}
for(String parent:parents){
path.add(parent);
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.