经典面试题:链表的相交与环问题

 经典面试题:链表的相交与环问题

1、 给出两个单向链表的头指针pHead1和pHead2,判断这两个链表是否相交。假设两个链表均不带环。
 示意图如下:

如果两个链表相交于某一节点,那么在这个相交节点之后的所有节点都是两个链表所共有的。也就是说,如果两个链表相交,那么最后一个节点肯定是共有的。先遍历第一个链表,记住最后一个节点,然后遍历第二个链表,到最后一个节点时和第一个链表的最后一个节点做比较,如果相同,则相交,否则不相交。时间复杂度为O( len1 + len2),因为只需要一个额外指针保存最后一个节点地址,空间复杂度为O(1)。(编程之美上面有详细的介绍)
2、给出一个单向链表的头指针pHead,判断链表中是否有环。
示意图如下:

链表中有环,其实也就是自相交。我们用两个指针pslow和pfast从头开始遍历链表,pslow每次前进一个节点,pfast每次前进两个结点,若存在环,则pslow和pfast肯定会在环中相遇,若不存在,则pslow和pfast能正常到达最后一个节点(实际上是到达NULL)。
代码如下:
[cpp]  view plain copy
  1. // 判断链表中是否有环  
  2. bool IsExitLoop(LinkList *head)  
  3. {  
  4.     LinkList *pslow = head;  
  5.     LinkList *pfast = head;  
  6.     while(pfast != NULL && pfast->next != NULL)  
  7.     {  
  8.         pslow = pslow->next;        // 每次前进一步  
  9.         pfast = pfast->next->next;  // 每次前进二步  
  10.         if(pslow == pfast)          // 两个指针相遇,说明存在环  
  11.             return true;  
  12.     }  
  13.     return false;    // 没有环  
  14. }  
3、给出两个单向链表的头指针pHead1和pHead2,判断这两个链表是否相交,若相交返回第一个相交的节点。假设两个链表均不带环。
方法一
判断两个链表中是否存在地址一致的节点,就可以知道是否相交了。可以对第一个链表的节点地址进行hash排序,建立hash表,然后针对第二个链表的每个节点的地址查询hash表,如果它在hash表中出现,则说明两个链表有共同的结点。这个方法的时间复杂度为:O(max(len1+len2);但同时还得增加O(len1)的存储空间存储哈希表。这样减少了时间复杂度,增加了存储空间。
以链表节点地址为值,遍历第一个链表,使用Hash保存所有节点地址值,结束条件为到最后一个节点(无环)或Hash中该地址值已经存在(有环)。
方法二:
对第一个链表遍历,计算长度len1,同时保存最后一个节点的地址。
对第二个链表遍历,计算长度len2,同时检查最后一个节点是否和第一个链表的最后一个节点相同,若不相同,则不相交,程序结束。
若相交,两个链表均从头节点开始,假设len1大于len2,那么将第一个链表先遍历len1-len2个节点,此时两个链表当前节点到第一个相交节点的距离就相等了,比较下一个节点是不是相同,如果相同就返回该节点(即相交节点),若不相同,两个链表都同步向后走一步,继续比较。
示意图如下:

方法三:
由于两个链表都没有环,我们可以把第二个链表接在第一个链表的后面,如果得到的链表有环,则说明这两个链表相交。否则,这两个链表不相交。这样我们就把问题转化为判断一个链表是否有环了。最后,当然可别忘记恢复原来的状态,去掉从第一个链表到第二个链表表头的指向。
4、给出一个单向链表的头指针pHead,判断链表中是否有环,若存在,则求出进入环中的第一个节点。
 示意图如下:

红色虚线框中的节点为待求节点。
首先使用第2个题目中的快、慢指针来判断链表是否存在环,若不存在结束。
若链表中存在环,我们从链表头、与两个指针的相遇点分别设一个指针,每次各走一步,两个指针必定相遇,且相遇的第一个点为环的入口点。

代码如下:
[cpp]  view plain copy
  1. // 找到环的第一个入口点  
  2. LinkList* FindLoopPort(LinkList *head)  
  3. {  
  4.     LinkList *pslow = head;  
  5.     LinkList *pfast = head;  
  6.     while(pfast != NULL && pfast->next != NULL)  
  7.     {  
  8.         pslow = pslow->next;        // 每次前进一步  
  9.         pfast = pfast->next->next;  // 每次前进二步  
  10.         if(pslow == pfast)          // 两个指针相遇,说明存在环  
  11.             break;  
  12.     }  
  13.     if(pfast == NULL || pfast->next == NULL)    // 不存在环  
  14.         return NULL;  
  15.     pslow = head;  
  16.     while(pslow != pfast)  
  17.     {  
  18.         pslow = pslow->next;        // 每次前进一步  
  19.         pfast = pfast->next;        // 每次前进一步  
  20.     }  
  21.     return pslow;       // 返回环的入口点  
  22. }  
分析:当pfast若与pslow相遇时,pslow肯定没有走遍历完链表,而pfast已经在环内循环了n圈(1<=n)。假设pslow走了s步,则pfast走了2s步(pfast步数还等于s 加上在环上多转的n圈),设环长为r,则:
 2s = s + nr    s= nr

设整个链表长L,入口环与相遇点距离为x,起点到环入口点的距离为a。   a + x = nr  则  a + x = (n – 1)r +r = (n-1)r + L - a   a = (n-1)r + (L – a – x)
(L – a – x)为相遇点到环入口点的距离,由此可知,从链表头到环入口点等于(n-1)循环内环+相遇点到环入口点,于是我们从链表头、与相遇点分别设一个指针,每次各走一步,两个指针必定相遇,且相遇第一点为环入口点。
小结:链表是数据结构中最基本的,也是面试中常考的,与链表相关的题目也变化多端,只要基础扎实,多积累一些处理类似问题的技巧,面试时便能应对自如。

单链表的的归并排序,同样需要找到链表的中间节点,可以使用前面的这个快、慢指针的方法。
[cpp]  view plain copy
  1. typedef struct LNode  
  2. {  
  3.     int data;  
  4.     struct LNode *next;  
  5. }LNode , *LinkList;  
  6.   
  7. // 对两个有序的链表进行递归的归并  
  8. LinkList MergeList_recursive(LinkList head1 , LinkList head2)  
  9. {  
  10.     LinkList result;  
  11.     if(head1 == NULL)  
  12.         return head2;  
  13.     if(head2 == NULL)  
  14.         return head1;  
  15.     if(head1->data < head2->data)  
  16.     {  
  17.         result = head1;  
  18.         result->next = MergeList_recursive(head1->next , head2);  
  19.     }  
  20.     else  
  21.     {  
  22.         result = head2;  
  23.         result->next = MergeList_recursive(head1 , head2->next);  
  24.     }  
  25.     return result;  
  26. }  
  27.   
  28. // 对两个有序的链表进行非递归的归并  
  29. LinkList MergeList(LinkList head1 , LinkList head2)  
  30. {  
  31.     LinkList head , result = NULL;  
  32.     if(head1 == NULL)  
  33.         return head2;  
  34.     if(head2 == NULL)  
  35.         return head1;  
  36.     while(head1 && head2)  
  37.     {  
  38.         if(head1->data < head2->data)  
  39.         {  
  40.             if(result == NULL)  
  41.             {  
  42.                 head = result = head1;  
  43.                 head1 = head1->next;  
  44.             }  
  45.             else  
  46.             {  
  47.                 result->next = head1;  
  48.                 result = head1;  
  49.                 head1 = head1->next;  
  50.             }  
  51.         }  
  52.         else  
  53.         {  
  54.             if(result == NULL)  
  55.             {  
  56.                 head = result = head2;  
  57.                 head2 = head2->next;  
  58.             }  
  59.             else  
  60.             {  
  61.                 result->next = head2;  
  62.                 result = head2;  
  63.                 head2 = head2->next;  
  64.             }  
  65.         }  
  66.     }  
  67.     if(head1)  
  68.         result->next = head1;  
  69.     if(head2)  
  70.         result->next = head2;  
  71.     return head;  
  72. }  
  73.   
  74. // 归并排序,参数为要排序的链表的头结点,函数返回值为排序后的链表的头结点  
  75. LinkList MergeSort(LinkList head)  
  76. {  
  77.     if(head == NULL)  
  78.         return NULL;  
  79.     LinkList r_head , slow , fast;  
  80.     r_head = slow = fast = head;  
  81.   
  82.     // 找链表中间节点的两种方法  
  83.     /* 
  84.     while(fast->next != NULL) 
  85.     { 
  86.         if(fast->next->next != NULL) 
  87.         { 
  88.             slow = slow->next; 
  89.             fast = fast->next->next; 
  90.         } 
  91.         else 
  92.             fast = fast->next; 
  93.     }*/  
  94.   
  95.     while(fast->next != NULL && fast->next->next != NULL)  
  96.     {  
  97.         slow = slow->next;  
  98.         fast = fast->next->next;  
  99.     }  
  100.       
  101.     if(slow->next == NULL)    // 链表中只有一个节点  
  102.         return r_head;  
  103.     fast = slow->next;  
  104.     slow->next = NULL;  
  105.     slow = head;  
  106.   
  107.     // 函数MergeList是对两个有序链表进行归并,返回值是归并后的链表的头结点  
  108.     //r_head = MergeList_recursive(MergeSort(slow) , MergeSort(fast));  
  109.     r_head = MergeList(MergeSort(slow) , MergeSort(fast));  
  110.     return r_head;  
  111. }  

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值