检查两个链表是否合并。如果是的话,在哪里?

时间:2009-10-20 11:51:11

标签: algorithm linked-list data-structures

这个问题可能已经过时了,但我想不出答案。

说,有两个不同长度的列表,在某一点合并;我们怎么知道合并点在哪里?

条件:

  1. 我们不知道长度
  2. 我们应该只解析每个列表一次。
  3. Example of two merged linked lists.

28 个答案:

答案 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

由于我们不知道长度,我们将计算xy而无需额外的迭代;你会看到如何。

然后,我们迭代每个列表并在迭代时反转它们!如果两个迭代器同时到达合并点,那么我们仅仅通过比较就可以找到它。否则,一个指针将在另一个指针之前到达合并点。

之后,当另一个迭代器到达合并点时,它将不会进入公共尾部。相反,将返回到之前达到合并点的列表的前一个开头!因此,在它到达更改列表的末尾(即另一个列表的前一个开头)之前,他将总共进行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)

如果我们可以精确地迭代列表两次,那么我可以提供确定合并点的方法:

  • 迭代两个列表并计算长度A和B
  • 计算长度差C = | A-B |;
  • 开始同时迭代这两个列表,但在列表中进行更多的其他C步骤
  • 这两个指针将在合并点相互见面

答案 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)

无需修改任何列表。有一个解决方案,我们只需要遍历每个列表一次。

  1. 创建两个堆栈,让我们说stck1和stck2。
  2. 遍历第一个列表并推送您在stck1中遍历的每个节点的副本。
  3. 与第二步相同,但这次遍历第二个列表并在stck2中推送节点副本。
  4. 现在,从两个堆栈弹出并检查两个节点是否相等,如果是,则保留对它们的引用。如果不是,那么之前相同的节点实际上就是我们正在寻找的合并点。

答案 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中的步骤:

  1. 创建地图。
  2. 开始遍历列表的两个分支,并使用与节点相关的一些唯一的东西(比如节点Id)作为Key将列表的所有遍历节点放入Map中,并将值作为1放在所有的开头。
  3. 当第一个重复键出现时,递增该键的值(现在说它的值变为2,即> 1。
  4. 获取值大于1的密钥,该密钥应该是两个列表合并的节点。

答案 24 :(得分:0)

这个怎么样:

  1. 如果只允许遍历每个列表一次,则可以创建一个新节点,遍历第一个列表以使每个节点都指向此新节点,并遍历第二个列表以查看是否有任何节点指向您的新节点(这是您的合并点)。如果第二次遍历没有导致您的新节点,则原始列表没有合并点。

  2. 如果允许多次遍历列表,则可以遍历每个列表以查找其长度,如果它们不同,则省略较长列表开头的“额外”节点。然后,只需逐步遍历两个列表,找到第一个合并节点。

答案 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)

如果允许编辑链接列表,

  1. 然后只将列表2的所有节点的下一个节点指针设为空。
  2. 查找列表1的最后一个节点的数据值。 这样一来,您就可以遍历两个列表的相交节点,并且没有“高逻辑”。

答案 27 :(得分:-1)

按照简单的逻辑解决这个问题: 由于指针 A 和 B 都以相同的速度行进。要在同一点相遇,它们必须相同的距离。我们可以通过将一个列表的长度添加到另一个列表来实现这一点。