zoukankan      html  css  js  c++  java
  • 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)

  • 相关阅读:
    XTU 1250 Super Fast Fourier Transform
    XTU 1249 Rolling Variance
    XTU 1243 2016
    CodeForces 710A King Moves
    CodeForces 710B Optimal Point on a Line
    HDU 4472 Count
    并查集
    高精度四件套
    Kruskal最小生成树
    [蓝桥杯]翻硬币(贪心的详解附带题目测试链接)
  • 原文地址:https://www.cnblogs.com/gqtcgq/p/7247177.html
Copyright © 2011-2022 走看看