编程之美--编程判断两个链表是否相交

输入一个单向链表,输出该链表中倒数第k个结点,
链表的倒数第0个结点为链表的尾指针。

相信,稍微有点 经验的同志,都会说到:设置两个指针p1,p2,首先p1和p2都指向head,然后p2向前走k步,这样p1和p2之间就间隔k个节点,最后p1和p2同时向前移动,直至p2走到链表末尾。

struct ListNode
{
    char data;
    ListNode* next;
};
ListNode* head,*p,*q;
ListNode *pone,*ptwo;


ListNode* fun(ListNode *head,int k)                                                
{
 assert(k >= 0);
 pone = ptwo = head;
 for( ; k > 0 && ptwo != NULL; k--)
  ptwo=ptwo->next;
 if (k > 0) return NULL;
 
 while(ptwo!=NULL)
 {
  pone=pone->next;
  ptwo=ptwo->next;
 }
 return pone;

这是针对链表单项链表查找其中倒数第k个结点。试问,如果链表是双向的,且可能存在环呢?编程判断两个链表是否相交。

 

给出两个单向链表的头指针(如下图所示),

比如h1、h2,判断这两个链表是否相交。这里为了简化问题,我们假设两个链表均不带环。

编程之美的解析:

  1. 直接循环判断第一个链表的每个节点是否在第二个链表中。但,这种方法的时间复杂度为O(Length(h1) * Length(h2))。显然,我们得找到一种更为有效的方法,至少不能是O(N^2)的复杂度。
  2. 针对第一个链表直接构造hash表,然后查询hash表,判断第二个链表的每个结点是否在hash表出现,如果所有的第二个链表的结点都能在hash表中找到,即说明第二个链表与第一个链表有相同的结点。时间复杂度为为线性:O(Length(h1) + Length(h2)),同时为了存储第一个链表的所有节点,空间复杂度为O(Length(h1))。是否还有更好的方法呢,既能够以线性时间复杂度解决问题,又能减少存储空间?
  3. 进一步考虑“如果两个没有环的链表相交于某一节点,那么在这个节点之后的所有节点都是两个链表共有的”这个特点,我们可以知道,如果它们相交,则最后一个节点一定是共有的。而我们很容易能得到链表的最后一个节点,所以这成了我们简化解法的一个主要突破口。那么,我们只要判断俩个链表的尾指针是否相等。相等,则链表相交;否则,链表不相交,O((Length(h1) + Length(h2))
  4. 上面的问题都是针对链表无环的,那么如果现在,链表是有环的呢?还能找到最后一个结点进行判断么?上面的方法还同样有效么?显然,这个问题的本质已经转化为判断链表是否有环。那么,如何来判断链表是否有环呢?

所以,事实上,这个判断两个链表是否相交的问题就转化成了:
1.
先判断带不带环
2.
如果都不带环,就判断尾节点是否相等
3.
如果都带环,判断一链表上俩指针相遇的那个节点,在不在另一条链表上。
如果在,则相交,如果不在,则不相交。

如何编写代码来判断链表是否有环呢?因为很多的时候,你给出了问题的思路后,面试官可能还要追加你的代码,ok,如下(设置两个指针(p1, p2),初始值都指向头,p1每次前进一步,p2每次前进二步,如果链表存在环,则p2先进入环,p1后进入环,两个指针在环中走动,必定相遇):

1.   //copyright@ KurtWang  

2.   //July2011.05.27  

3.   struct Node  

4.   {  

5.       int value;  

6.       Node * next;  

7.   };  

8.     

9.   //1.先判断带不带环  

10.  //判断是否有环,返回bool,如果有环,返回环里的节点  

11.  //思路:用两个指针,一个指针步长为1,一个指针步长为2,判断链表是否有环  

12.  bool isCircle(Node * head, Node *& circleNode, Node *& lastNode)  

13.  {  

14.      Node * fast = head->next;  

15.      Node * slow = head;  

16.      while(fast != slow && fast && slow)  

17.      {  

18.          if(fast->next != NULL)  

19.              fast = fast->next;  

20.            

21.          if(fast->next == NULL)  

22.              lastNode = fast;  

23.          if(slow->next == NULL)  

24.              lastNode = slow;  

25.            

26.          fast = fast->next;  

27.          slow = slow->next;  

28.            

29.      }  

30.      if(fast == slow && fast && slow)  

31.      {  

32.          circleNode = fast;  

33.          return true;  

34.      }  

35.      else  

36.          return false;  

37.  }

如果都不带环,就判断尾节点是否相等,如果都带环,判断一链表上俩指针相遇的那个节点,在不在另一条链表上。下面是综合解决这个问题的代码:

1.   //判断带环不带环时链表是否相交  

2.   //2.如果都不带环,就判断尾节点是否相等  

3.   //3.如果都带环,判断一链表上俩指针相遇的那个节点,在不在另一条链表上。  

4.   bool detect(Node * head1, Node * head2)  

5.   {  

6.       Node * circleNode1;  

7.       Node * circleNode2;  

8.       Node * lastNode1;  

9.       Node * lastNode2;  

10.        

11.      bool isCircle1 = isCircle(head1,circleNode1, lastNode1);  

12.      bool isCircle2 = isCircle(head2,circleNode2, lastNode2);  

13.        

14.      //一个有环,一个无环  

15.      if(isCircle1 != isCircle2)  

16.          return false;  

17.      //两个都无环,判断最后一个节点是否相等  

18.      else if(!isCircle1 && !isCircle2)  

19.      {  

20.          return lastNode1 == lastNode2;  

21.      }  

22.      //两个都有环,判断环里的节点是否能到达另一个链表环里的节点  

23.      else  

24.      {  

25.          Node * temp = circleNode1->next;  //updated,多谢苍狼 and hyy  

26.          while(temp != circleNode1)    

27.          {  

28.              if(temp == circleNode2)  

29.                  return true;  

30.              temp = temp->next;  

31.          }  

32.          return false;  

33.      }  

34.        

35.      return false;  

36.  }

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值