这个问题可能已经过时了,但我想不出答案。
说,有两个不同长度的列表,在某一点合并;我们怎么知道合并点在哪里?
条件:
答案 0 :(得分:120)
以下是迄今为止我见过的最伟大的一个 - O(N),没有计数器。我在接受候选人S.N.的采访时得到了它。在VisionMap。
制作一个这样的交互指针:它每次都向前移动直到结束,然后跳转到相反列表的开头,依此类推。 创建其中两个,指向两个头。 每次将每个指针前进1,直到它们相遇。这将在经过一到两次之后发生。
我仍然在采访中使用这个问题 - 但要看看有多长时间才能理解为什么这个解决方案有效。
答案 1 :(得分:89)
Pavel的回答需要修改列表以及迭代每个列表两次。
这是仅需要迭代每个列表两次的解决方案(第一次计算它们的长度;如果给出长度,则只需要迭代一次)。
这个想法是忽略较长列表的起始条目(合并点不能存在),这样两个指针距离列表末尾的距离相等。然后将它们向前移动直到它们合并。
lenA = count(listA) //iterates list A
lenB = count(listB) //iterates list B
ptrA = listA
ptrB = listB
//now we adjust either ptrA or ptrB so that they are equally far from the end
while(lenA > lenB):
ptrA = ptrA->next
lenA--
while(lenB > lenA):
prtB = ptrB->next
lenB--
while(ptrA != NULL):
if (ptrA == ptrB):
return ptrA //found merge point
ptrA = ptrA->next
ptrB = ptrB->next
这与我的其他答案渐近相同(线性时间)但可能具有较小的常数,因此可能更快。但我认为我的另一个答案更酷。
答案 2 :(得分:35)
如果
以下算法将成为解决方案。
首先是数字。假设第一个列表的长度为a+c
,第二个列表的长度为b+c
,其中c
是其公共“尾部”的长度(在合并点之后)。我们将它们表示如下:
x = a+c
y = b+c
由于我们不知道长度,我们将计算x
和y
而无需额外的迭代;你会看到如何。
然后,我们迭代每个列表并在迭代时反转它们!如果两个迭代器同时到达合并点,那么我们仅仅通过比较就可以找到它。否则,一个指针将在另一个指针之前到达合并点。
之后,当另一个迭代器到达合并点时,它将不会进入公共尾部。相反,将返回到之前达到合并点的列表的前一个开头!因此,在它到达更改列表的末尾(即另一个列表的前一个开头)之前,他将总共进行a+b+1
次迭代。我们称之为z+1
。
首先到达合并点的指针将继续迭代,直到到达列表的末尾。应该计算它所做的迭代次数,并且等于x
。
然后,该指针重新迭代并再次反转列表。但现在它不会回到它最初开始的列表的开头!相反,它将进入另一个列表的开头!应计算其迭代次数,并等于y
。
所以我们知道以下数字:
x = a+c
y = b+c
z = a+b
我们确定
a = (+x-y+z)/2
b = (-x+y+z)/2
c = (+x+y-z)/2
解决了这个问题。
答案 3 :(得分:28)
好吧,如果你知道他们会合并:
说你开始:
A-->B-->C
|
V
1-->2-->3-->4-->5
1)浏览第一个列表,将每个下一个指针设置为NULL。
现在你有:
A B C
1-->2-->3 4 5
2)现在浏览第二个列表并等到看到NULL,这是你的合并点。
如果你不能确定它们是否合并,你可以使用指针值的sentinel值,但这不是那么优雅。
答案 4 :(得分:12)
如果我们可以精确地迭代列表两次,那么我可以提供确定合并点的方法:
答案 5 :(得分:7)
这是一个解决方案,计算速度快(迭代每个列表一次)但使用大量内存:
for each item in list a
push pointer to item onto stack_a
for each item in list b
push pointer to item onto stack_b
while (stack_a top == stack_b top) // where top is the item to be popped next
pop stack_a
pop stack_b
// values at the top of each stack are the items prior to the merged item
答案 6 :(得分:6)
这可以说违反了“仅解析每个列表一次”的条件,但实现了tortoise and hare algorithm(用于查找循环列表的合并点和循环长度),因此从列表A开始,到达时最后的NULL假装它是指向列表B开头的指针,从而创建循环列表的外观。然后,算法将告诉您合并所在的列表A的确切位置(根据维基百科描述,变量“mu”)。
此外,“lambda”值告诉您列表B的长度,如果需要,您可以在算法期间计算列表A的长度(当您重定向NULL链接时)。
答案 7 :(得分:6)
您可以使用一组节点。迭代一个列表并将每个节点添加到集合中。然后遍历第二个列表,并且对于每次迭代,检查集合中是否存在节点。如果是,您已找到合并点:)
答案 8 :(得分:3)
我在FC9 x86_64上测试了一个合并案例,并打印每个节点地址,如下所示:
Head A 0x7fffb2f3c4b0
0x214f010
0x214f030
0x214f050
0x214f070
0x214f090
0x214f0f0
0x214f110
0x214f130
0x214f150
0x214f170
Head B 0x7fffb2f3c4a0
0x214f0b0
0x214f0d0
0x214f0f0
0x214f110
0x214f130
0x214f150
0x214f170
注意因为我已经对齐了节点结构,所以当malloc()是一个节点时,地址对齐为16字节,请看至少4位。 最小位是0,即0x0或000b。 因此,如果您处于相同的特殊情况(对齐的节点地址),则可以使用这些至少4位。 例如,当从头到尾旅行两个列表时,设置访问节点地址的4位中的1或2,即设置标志;
next_node = node->next;
node = (struct node*)((unsigned long)node | 0x1UL);
注意上面的标志不会影响实际节点地址,只会影响SAVED节点指针值。
一旦发现有人设置了标志位,那么第一个找到的节点应该是合并点。 完成后,您将通过清除已设置的标志位来恢复节点地址。虽然重要的是你在迭代时应该小心(例如node = node-> next)来做清理。记住你已经设置了标志位,所以这样做
real_node = (struct node*)((unsigned long)node) & ~0x1UL);
real_node = real_node->next;
node = real_node;
由于此提案将恢复已修改的节点地址,因此可以将其视为“无修改”。
答案 9 :(得分:3)
也许我过度简化了这一点,但只是迭代最小的列表并使用最后一个节点Link
作为合并点?
因此,Data->Link->Link == NULL
是终点,将Data->Link
作为合并点(在列表末尾)。
编辑:
好的,从您发布的图片中,您解析了两个列表,最小的列表。使用最小的列表,您可以维护对以下节点的引用。现在,当您解析第二个列表时,您在引用上进行比较,以找到Reference [i]是LinkedList [i] - > Link的引用的位置。这将给出合并点。是时候用图片来解释了(将图片上的值叠加在OP上)。
您有一个链接列表(参考如下所示):
A->B->C->D->E
您有第二个链接列表:
1->2->
使用合并列表,引用将如下所示:
1->2->D->E->
因此,您映射第一个“较小”列表(作为合并列表,我们所计算的长度为4,主列表为5)
循环遍历第一个列表,维护引用的引用。
该列表将包含以下参考Pointers { 1, 2, D, E }
。
我们现在通过第二个清单:
-> A - Contains reference in Pointers? No, move on
-> B - Contains reference in Pointers? No, move on
-> C - Contains reference in Pointers? No, move on
-> D - Contains reference in Pointers? Yes, merge point found, break.
当然,你维护一个新的指针列表,但这不在规范之外。但是,第一个列表只被解析一次,而第二个列表只有在没有合并点时才会被完全解析。否则,它将更快结束(在合并点)。
答案 10 :(得分:2)
可以有一个简单的解决方案,但需要辅助空间。这个想法是遍历一个列表并将每个地址存储在一个哈希图中,现在遍历另一个列表并匹配该地址是否在哈希图中。每个列表仅被遍历一次。没有任何列表的修改。长度仍然未知。使用的辅助空间:O(n),其中“ n”是所遍历的第一个列表的长度。
答案 11 :(得分:2)
这个解决方案只迭代每个列表一次......也不需要修改列表..虽然你可能会抱怨空间..
1)基本上你在list1中迭代并将每个节点的地址存储在一个数组中(存储unsigned int值)
2)然后你迭代list2,并为每个节点的地址--->你搜索你找到匹配的数组...如果你这样做,那么这就是合并节点
//pseudocode
//for the first list
p1=list1;
unsigned int addr[];//to store addresses
i=0;
while(p1!=null){
addr[i]=&p1;
p1=p1->next;
}
int len=sizeof(addr)/sizeof(int);//calculates length of array addr
//for the second list
p2=list2;
while(p2!=null){
if(search(addr[],len,&p2)==1)//match found
{
//this is the merging node
return (p2);
}
p2=p2->next;
}
int search(addr,len,p2){
i=0;
while(i<len){
if(addr[i]==p2)
return 1;
i++;
}
return 0;
}
希望这是一个有效的解决方案......
答案 12 :(得分:1)
无需修改任何列表。有一个解决方案,我们只需要遍历每个列表一次。
答案 13 :(得分:0)
您可以将list1
的节点添加到哈希集中,然后通过第二个循环(如果集合中已经存在list2
的任何节点)进行循环。如果是,则为合并节点>
static int findMergeNode(SinglyLinkedListNode head1, SinglyLinkedListNode head2) {
HashSet<SinglyLinkedListNode> set=new HashSet<SinglyLinkedListNode>();
while(head1!=null)
{
set.add(head1);
head1=head1.next;
}
while(head2!=null){
if(set.contains(head2){
return head2.data;
}
}
return -1;
}
答案 14 :(得分:0)
使用javascript解决方案
var getIntersectionNode = function(headA, headB) {
if(headA == null || headB == null) return null;
let countA = listCount(headA);
let countB = listCount(headB);
let diff = 0;
if(countA > countB) {
diff = countA - countB;
for(let i = 0; i < diff; i++) {
headA = headA.next;
}
} else if(countA < countB) {
diff = countB - countA;
for(let i = 0; i < diff; i++) {
headB = headB.next;
}
}
return getIntersectValue(headA, headB);
};
function listCount(head) {
let count = 0;
while(head) {
count++;
head = head.next;
}
return count;
}
function getIntersectValue(headA, headB) {
while(headA && headB) {
if(headA === headB) {
return headA;
}
headA = headA.next;
headB = headB.next;
}
return null;
}
答案 15 :(得分:0)
我们可以使用两个指针,并以某种方式移动,如果其中一个指针为null,则将其指向另一个列表的头部,另一个指向相同的指针,这样,如果列表长度不同,它们将在第二遍。
如果list1的长度为n,list2的长度为m,则它们的差为d = abs(n-m)。他们将覆盖此距离并在合并点会合。
代码:
int findMergeNode(SinglyLinkedListNode* head1, SinglyLinkedListNode* head2) {
SinglyLinkedListNode* start1=head1;
SinglyLinkedListNode* start2=head2;
while (start1!=start2){
start1=start1->next;
start2=start2->next;
if (!start1)
start1=head2;
if (!start2)
start2=head1;
}
return start1->data;
}
答案 16 :(得分:0)
int FindMergeNode(Node headA, Node headB) {
Node currentA = headA;
Node currentB = headB;
// Do till the two nodes are the same
while (currentA != currentB) {
// If you reached the end of one list start at the beginning of the other
// one currentA
if (currentA.next == null) {
currentA = headB;
} else {
currentA = currentA.next;
}
// currentB
if (currentB.next == null) {
currentB = headA;
} else {
currentB = currentB.next;
}
}
return currentB.data;
}
答案 17 :(得分:0)
这是一个真正的问题!如果我们在sql数据库中有树形结构。我们想在SQL触发器中找到LCA。
在触发器环境中,我们无法创建临时表,因为它将隐式提交事务。因此我们必须在O(1)内存中执行此操作,即只能使用变量。
答案 18 :(得分:0)
O(n)复杂度解决方案。但是基于一个假设。
假设是:两个节点都只有正整数。
logic:将list1的所有整数都设为负数。然后遍历list2,直到得到一个负整数。找到后=>拿下它,将符号改回正数并返回。
static int findMergeNode(SinglyLinkedListNode head1, SinglyLinkedListNode head2) {
SinglyLinkedListNode current = head1; //head1 is give to be not null.
//mark all head1 nodes as negative
while(true){
current.data = -current.data;
current = current.next;
if(current==null) break;
}
current=head2; //given as not null
while(true){
if(current.data<0) return -current.data;
current = current.next;
}
}
答案 19 :(得分:0)
我们可以通过引入&#34; isVisited&#34;来有效地解决它。领域。遍历第一个列表并设置&#34; isVisited&#34;价值为&#34;真&#34;对于所有节点直到结束。现在从第二个开始,找到第一个标志为true的节点和Boom,它是你的合并点。
答案 20 :(得分:0)
使用“地图”或“词典”存储地址或节点的值。如果地址/字典中存在地址,那么键的值就是答案。 我这样做了:
int FindMergeNode(Node headA, Node headB) {
Map<Object, Integer> map = new HashMap<Object, Integer>();
while(headA != null || headB != null)
{
if(headA != null && map.containsKey(headA.next))
{
return map.get(headA.next);
}
if(headA != null && headA.next != null)
{
map.put(headA.next, headA.next.data);
headA = headA.next;
}
if(headB != null && map.containsKey(headB.next))
{
return map.get(headB.next);
}
if(headB != null && headB.next != null)
{
map.put(headB.next, headB.next.data);
headB = headB.next;
}
}
return 0;
}
答案 21 :(得分:0)
int FindMergeNode(Node *headA, Node *headB)
{
Node *tempB=new Node;
tempB=headB;
while(headA->next!=NULL)
{
while(tempB->next!=NULL)
{
if(tempB==headA)
return tempB->data;
tempB=tempB->next;
}
headA=headA->next;
tempB=headB;
}
return headA->data;
}
答案 22 :(得分:0)
第1步:找到列表中的长度 第2步:找到差异并移动最大的列表与差异 第3步:现在两个列表都处于类似的位置。 第4步:遍历列表以找到合并点
//Psuedocode
def findmergepoint(list1, list2):
lendiff = list1.length() > list2.length() : list1.length() - list2.length() ? list2.lenght()-list1.lenght()
biggerlist = list1.length() > list2.length() : list1 ? list2 # list with biggest length
smallerlist = list1.length() < list2.length() : list2 ? list1 # list with smallest length
# move the biggest length to the diff position to level both the list at the same position
for i in range(0,lendiff-1):
biggerlist = biggerlist.next
#Looped only once.
while ( biggerlist is not None and smallerlist is not None ):
if biggerlist == smallerlist :
return biggerlist #point of intersection
return None // No intersection found
答案 23 :(得分:0)
Java中的步骤:
答案 24 :(得分:0)
这个怎么样:
如果只允许遍历每个列表一次,则可以创建一个新节点,遍历第一个列表以使每个节点都指向此新节点,并遍历第二个列表以查看是否有任何节点指向您的新节点(这是您的合并点)。如果第二次遍历没有导致您的新节点,则原始列表没有合并点。
如果允许多次遍历列表,则可以遍历每个列表以查找其长度,如果它们不同,则省略较长列表开头的“额外”节点。然后,只需逐步遍历两个列表,找到第一个合并节点。
答案 25 :(得分:0)
这是天真的解决方案,无需遍历整个列表。
如果您的结构化节点有三个字段,如
struct node {
int data;
int flag; //initially set the flag to zero for all nodes
struct node *next;
};
说你有两个头(head1和head2)指向两个列表的头部。
以相同的速度遍历列表,并为该节点放置标志= 1(访问标志),
if (node->next->field==1)//possibly longer list will have this opportunity
//this will be your required node.
答案 26 :(得分:-1)
如果允许编辑链接列表,
答案 27 :(得分:-1)
按照简单的逻辑解决这个问题: 由于指针 A 和 B 都以相同的速度行进。要在同一点相遇,它们必须相同的距离。我们可以通过将一个列表的长度添加到另一个列表来实现这一点。