检查两个链表是否相交,如果相交,找出交点的位置。

114

这个问题可能有些老,但我想不出答案。

假设有两个不同长度的列表,在某一点 合并;我们如何知道它们的合并点在哪里?

条件:

  1. 我们不知道长度
  2. 我们只能遍历每个列表一次。

两个合并链表的示例。


1
我非常确定,如果不修改列表,它是无法正常工作的。(或者将其复制到其他地方以避免仅解析一次的限制。) - Georg Schölly
2
可能就是这个意思。该死的面试官!呵呵 - Kyle Rosendo
1
我有一个有趣的提议...假设列表的公共尾部是无限长的。如何使用恒定的内存找到节点交集? - Akusete
在不修改列表、使用计数器或进行超过O(N)次迭代的情况下,下面发布了一个解决方案。 - Pavel Radzivilovsky
一个使用golang编写的时间复杂度为O(n^2)和O(n)的示例,后者使用了一个集合: https://play.golang.org/p/XpK3D_p-oq - jpgerek
显示剩余6条评论
27个回答

3

这里有一个简单的解决方案,但需要使用辅助空间。思路是遍历一个列表并将每个地址存储在哈希映射中,现在遍历另一个列表并匹配地址是否存在于哈希映射中。每个列表只被遍历一次,不对任何列表进行修改,长度仍未知。所需辅助空间:O(n),其中'n'是遍历第一个列表的长度。


2
这个解决方案只需要遍历每个列表一次...不需要修改列表。尽管你可能会抱怨空间..
1) 基本上你在list1中进行迭代,并将每个节点的地址存储在一个数组中(该数组存储无符号整数值)
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;
} 

希望这是一个有效的解决方案...


这基本上是对一个列表进行迭代,但使用数组的形式而不是列表本身。 - syockit

1

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

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

1
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 = headA;
    } else {
      currentA = currentA.next;
    }
    // currentB
    if (currentB.next == null) {
      currentB = headB;
    } else {
      currentB = currentB.next;
    }
  }
  return currentB.data;
}

在最初的修订中,这只是简单地阐述了得票最高的答案(Pavel Radzivilovsky,2013) - greybeard

1
我们可以使用两个指针并以一种方式移动,使得如果其中一个指针为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;
}

0
一种O(n)复杂度的解决方案。但是基于一个假设。
假设是:两个节点都只有正整数。
逻辑:将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;
    }

}

0

使用Map或Dictionary来存储节点的地址和值。如果地址已经存在于Map/Dictionary中,则键的值就是答案。 我已经这样做了:

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;
}

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. 

0

这样怎么样:

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

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


  1. 不仅修改了第一个列表,还将其破坏。
  2. 一再建议使用。
- greybeard

0
我们可以通过引入“isVisited”字段来高效地解决它。首先遍历第一个列表,并将所有节点的“isVisited”值设置为“true”,直到结束。现在从第二个列表开始,找到第一个标志为真的节点,那么这就是你的合并点。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接