10链表追赶问题

一:求链表倒数第k个结点

   题目描述:输入一个单向链表,输出该链表中倒数第k个结点,链表的倒数第0个结点为链表的尾指针(NULL)。

    思路设置两个指针p1,p2,首先p1和p2都指向head,然后p2向前走k步,这样p1和p2之间就间隔k个节点,最后p1和p2同时向前移动,直至p2走到链表末尾。

    代码:

         ListNode* fun(ListNode *head,int k)

         {     

                  ListNode *pone, *ptwo;

                  assert(k>= 0);

                  pone= ptwo = head;

                  for(; k > 0 && ptwo != NULL; k--)

                  {

                        ptwo=ptwo->next;

               }

     

                 if(k > 0)return NULL;               //k大于链表总长度

 

                 while(ptwo != NULL)

                  {

                           pone=pone->next;

                           ptwo=ptwo->next;

                  }

                 return pone;

         } 

 

二:编程判断两个链表是否相交(不包括环)
   题目描述
:给出两个单向链表的头指针(如下图所示),比如h1、h2,判断这两个链表是否相交。这里为了简化问题,先假设两个链表均不带环

 

    思路:

    1:直接循环判断第一个链表的每个节点是否在第二个链表中。但这种方法的时间复杂度为O(Length(h1) * Length(h2))。显然,我们得找到一种更为有效的方法。

    2:针对第一个链表直接构造hash表,然后查询hash表,判断第二个链表的每个结点是否在hash表出现,如第二个链表的结点能在hash表中找到,即说明第二个链表与第一个链表有相同的结点。时间复杂度为为线性:O(Length(h1) + Length(h2)),同时为了存储第一个链表的所有节点,空间复杂度为O(Length(h1))。是否还有更好的方法呢,既能够以线性时间复杂度解决问题,又能减少存储空间?

    3:进一步考虑“如果两个没有环的链表相交于某一节点,那么在这个节点之后的所有节点都是两个链表共有的”这个特点,我们可以知道,如果它们相交,则最后一个节点一定是共有的。而我们很容易能得到链表的最后一个节点,所以这成了我们简化解法的一个主要突破口。那么,我们只要判断俩个链表的尾指针是否相等。相等,则链表相交;否则,链表不相交。

    所以,先遍历第一个链表,记住最后一个节点。然后遍历第二个链表,到最后一个节点时和第一个链表的最后一个节点做比较,如果相同,则相交,否则,不相交。这样我们就得到了一个时间复杂度为O((Length(h1)+ Length(h2))而且只用了一个额外的指针来存储最后一个节点。这个方法时间复杂度为线性O(N),空间复杂度为O(1)。

 

代码如下:

         int  isListCross(ListNode *h1, ListNode *h2)

         {

                  ListNode*p = h1;

                  ListNode*q = h2;

 

                  while(p->next!= NULL)

                  {

                           p = p->next;

                  }

                  while(q->next != NULL)

                  {

                           q = q->next;

                  }

 

                  if(p== q)return       TRUE;

                  return    FALSE;

         }

 

三:判断单链表是否带环
   题目描述
:给出单向链表的头指针,判断这个链表是否包括环。

    思路:设置两个指针(fast, slow),初始值都指向头,slow每次前进一步,fast每次前进二步,如果链表存在环,则fast必定先进入环,而slow后进入环,两个指针必定相遇。(当然,fast先行头到尾部为NULL,则为无环链表)
    代码

int  isListHasLoop(ListNode *head)

{

         ListNode *fast, slow;

 

         fast = slow = head;

         while(fast !=NULL && fast->next != NULL)

         {

                  fast = fast->next;

                  fast = fast->next;

                  slow = slow->next;

 

                  if(fast == slow)

                  {

                          break;

                  }

         }

         if(fast == NULL || fast->next ==NULL)

         {

                  return FALSE;

         }

         return TRUE;

}

    上述快慢指针的方法,也可以快速找到无环链表的中间节点,同样是快指针走两步,慢指针走一步,当快指针走到链表尾部的时候,慢指针正好走到了链表的中间。

 

四:找出带环链表的环入口点

   题目描述:给出单向链表的头指针,判断这个链表是否包括环,如果包括环的话,找出环的入口点。

    思路:首先判断链表是否包括环,如果包括环的话,则判断的同时,也找到了快慢指针的碰撞点,可以证明:碰撞点p到入口点的距离等于头指针到入口点的距离,因此:分别从碰撞点、头指针开始走,相遇的那个点就是环入口点。

   证明:链表形状类似字母“b”。假设从链表头到入口点的长度为 a(结点个数),环长度为 b 。则总长度(也是总结点数)为 a+b 。

    从头开始对结点进行编号,将第 i 步访问的结点编号用 S(i) 表示。i = 0, 1 ...

    当 i<a 时,S(i)=i ;

    当 i≥a 时,S(i)=a+(i-a)%b 。

    分析追赶过程:两个指针分别前进,假定经过 x 步后,快慢指针碰撞。此时快指针经过了2x个结点,慢指针经过了x个结点,所以:S(x)=S(2x)

    因为碰撞点肯定在环内,所以,x≥a,因而:S(x) = a+(x-a)%b,S(2x) = a+(2x-a)%b。

a+(x-a)% b = a+(2x-a)%b  --> x=tb 。
    入口点为从起点走 a 步,即 S(a)。

    从碰撞点开始走a步,S(x+a) = S(tb+a) = S(a)。

    得到结论:从碰撞点、头指针开始走,相遇的那个点就是环入口点

 

    代码

ListNode  *LoopPoint(ListNode *head)

{

         ListNode *fast, slow;

 

         fast = slow = head;

 

         while(fast != NULL &&fast->next != NULL)

         {

                  fast = fast->next;

                  fast = fast->next;

                  slow = slow->next;

                  if(fast == slow)

                  {

                          break;

                  }

         }

         if(fast == NULL || fast->next ==NULL)

         {

                  return NULL;

         }

 

         slow = head;

         while(slow !=fast)

         {

                  slow =slow->next;

                  fast =fast->next;

         }

         return fast;

}

 

五:考虑两个链表是否相交,考虑有环的情况

    上面的链表相交问题都是针对链表无环的,那么如果链表是有环的呢?上面的方法就变的无效了。

    如果链表有环的话,那么如果两个链表相交,则环一定是链表共有的部分。所以,如果考虑的链表存在有环的情况,那么这个判断两个链表是否相交的问题就转化成了:
    1.先判断带不带环
    2.
如果都不带环,就判断尾节点是否相等
    3.如果都带环,判断其中之一的碰撞点,经过几步后能否碰到另外一个碰撞点。如果能,则相交,如果不能,则不相交。

 

    代码:

int  isListHasLoop2(ListNode *head, ListNode **lastnode, ListNode **meet)

{

         ListNode  *fast, *slow;

 

         *lastnode = NULL;

         *meet = NULL;

 

         fast = slow = head;

 

         while(fast != NULL)

         {

                  if(fast->next!= NULL)

                  {

                          fast= fast->next;

                  }

                  if(fast->next== NULL)

                  {

                          *lastnode= fast;

                          break;

                  }

                 

                  fast = fast->next;

                  slow = slow->next;

                  if(fast == slow)

                  {

                          *meet = fast;

                          break;

                  }

         }

         if(fast == NULL || fast->next ==NULL)

         {

                  return FALSE;

         }

         return TRUE;

}

 

int  isListCross2(ListNode *h1, ListNode *h2)

{

         ListNode *lastnode1, *lastnode2;

         ListNode *m1, *m2;

         ListNode *flag;

 

         int hasloop1, hasloop2;

         hasloop1 = isListHasLoop2(h1,&lastnode1, &m1);

         hasloop2 = isListHasLoop2(h1,&lastnode2, &m2);

 

         if(hasloop1 !=hasloop2)

         {

                  returnFALSE;

         }

         if(hasloop1 == FALSE)

         {

                  return lastnode1 == lastnode2

         }

         else

         {

                  flag = m1;

                  for(;;)

                  {

                          if(m1== m2)return TRUE;

                          m1= m1->next;

                          if(m1== flag)

                          {

                                   returnFALSE;

                          }

                  }

         }

}

 

六:两个链表相交的第一个节点

       思路:这个问题有几种特殊情况,需要分别考虑

       如果链表有环,则计算出环的长度looplen,以及两个链表的总长度len1和len2。

       特殊情况

       如果len1 (或len2)== looplen,表明链表1就是链表2的环,所以,链表2的入口点就是相交的第一个节点。

       如果len1 = len2 = looplen,表明链表1和链表2都是环,完全重合,则任意结点都可是相交的第一个节点。

      

       排除特殊情况后,则分别计算从链表头到环入口点的长度,如果链表没有环,则计算链表总长度。分别记为len1和len2

       假设为len1较大,则对于链表1,先从h1开始走len1-len2步,然后,链表2和链表1在同时走,如果碰到结点相同的时刻,则便是两个链表相交的第一个节点。

 

七:机器人相遇问题

    题目描述:在一条左右水平放置的直线轨道上任选两个点,放置两个机器人,请用如下指令系统为机器人设计控制程序,使这两个机器人能够在直线轨道上相遇。(注意两个机器人用你写的同一个程序来控制
    指令系统:只包含4条指令,向左、向右、条件判定、无条件跳转。其中向左(右)指令每次能控制机器人向左(右)移动一步;条件判定指令能对机器人所在的位置进行条件测试,测试结果是如果对方机器人曾经到过这里就返回true,否则返回false;无条件跳转,类似汇编里面的跳转,可以跳转到任何地方。

 

    思路:两个机器人用你写的同一个程序来控制,也就是说机器人需要运行同样的代码。

    首先题目要求很简单,就是要你想办法让A最终能赶上B,A在后,B在前,都向右移动,如果它们的速度永远一致,那A是永远无法追赶上B的。但题目给出了一个“条件判断指令”,即如果A或B某个机器人向前移动时,若是某个机器人经过的点是第二个机器人曾经经过的点,那么程序返回true。就是抓住这一点,A 到达曾经B经过的点后,发现此后的路是B此前经过的,那么A开始提速两倍,B一直保持原来的一倍速度不变,那样的话,A势必会追上B。

    简单伪代码如下:

start:
if(at the position other robots have not reached)
    move_right
else

   move_right
    move_right
goto start

 

    有个细节又出现了,上面这个分支不一定能提速的。why?因为如果if条件花的时间很少,而move指令发的时间很大(实际很可能是这样),那么两个机器人的速度还是基本是一样的。那作如何修改呢?

start:
if(at the position other robots have not reached)
    move_right
    move_left
    move_right
else
    move_right
goto start

 

 

(http://blog.csdn.net/v_JULY_v/article/details/6447013)

原文地址:https://www.cnblogs.com/gqtcgq/p/7247177.html