链表快慢指针应用

链表中倒数第k个结点

本题从1开始计数,即链表的尾结点是倒数第一个结点,例如链表从头结点依次是1,2,3,4,5,6,则倒数第3个结点是4

struct ListNode
{
     int val;
     ListNode* next;
};
利用快慢指针解题,先让一个指针走k-1步,然后两个指针同时走,当快指针走到链表尾部时,慢指针指向的位置就是倒数第k个结点所在位置

ListNode* FindKToTail(ListNode* pHead, unsigned int k)
{
    if(pHead == NULL || k == 0)
         return NULL;
    ListNode* pFast = pHead;
    ListNode* pSlow = pHead;
    for(unsigned int i = 0; i < k-1; i++)/*当k== 0时,此处循环次数增加好几倍,不符合*/
    {
         if(pFast->next != NULL)
              pFast = pFast->next; 
         else  /*结点数小于k*/
              return NULL;
    }
    while(pFast->next != NULL)
    {
          pFast = pFast->next;
          pSlow =  pSlow->next;
    }
    return pSlow;
}

查找链表中间结点

思路:用快慢指针,块指针一次走两步,慢指针一次走一步。块指针到链表尾部时,慢指针刚好到中间结点。如果链表数为奇数,返回中间结点,如果为偶数,返回中间结点的任意一个。

ListNode* FindMiddleNode(ListNode* pHead)
{
    if(pHead == NULL)
        return NULL;
    ListNode* pFast = pHead;
    ListNode* pSlow = pHead;
    while(pFast != NULL && pFase->next != NULL)
     {
           pFast = pFast->next->next;
           pSlow = pSlow->next;
      }
    return pSlow;
}
判断单链表是否有环

思路:用快慢指针,块指针一次走两步,慢指针一次走一步。如果走的块的指针追上走得慢的,说明有环,否则没有

bool isLoop(ListNode* pHead)
{
    if(pHead == NULL)
         return false;
    ListNode* pFast = pHead;
    ListNode* pSlow = pHead;
    while(pFast != NULL && pFast->next != NULL)
     {
           pFast = pFast->next->next;
           pSlow = pSlow->next;
           if(pFast == pSlow)
                 break;
      }
/* 如果无环,则pFast先走到终点,当链表长度为奇数时,pFast->next为空,当链表长度为偶数时,pFast为空*/
      if(pFast == NULL || pFast->next == NULL)
      <span style="white-space:pre">	</span>return false;
     else
      <span style="white-space:pre">	</span>return true;
}
求单链表环长

思路:在环上相遇后,记录第一次相遇点为Pos,之后指针slow继续每次走1步,fast每次走2步。在下次相遇的时候fast比slow正好又多走了一圈,也就是多走的距离等于环长。

设从第一次相遇到第二次相遇,设slow走了len步,则fast走了2*len步,相遇时多走了一圈:

   环长=2*len-len。

int loopLength(ListNode* pHead)
{
    if(!isLoop(pHead))
       return 0;
    ListNode* fast= pHead;
    ListNode* slow = pHead;
    int len = 0;
    bool first  = false; //标记第一次相遇
    bool second = false; //标记第二圈相遇
    while(fast != NULL && fast->next != NULL)
    {
          fast = fast->next->next;
          slow = slow->next;
          //超两圈后停止计数
          if(fast == slow && second )
               break;
          //超一圈 开始计数
          if(fast == slow && !second)
          {
                first = true;
                second = true;
           }
           //计数
          if(first)
             ++len;
    }
    return len;
}
求有环链表环结点位置

第一次碰撞点Pos到连接点Join的距离=头指针到连接点Join的距离,因此,分别从第一次碰撞点Pos、头指针head开始走,相遇的那个点就是连接点。

     

  在环上相遇后,记录第一次相遇点为Pos,连接点为Join,假设头结点到连接点的长度为LenA,连接点到第一次相遇点的长度为x,环长为R

    第一次相遇时,slow走的长度 S = LenA + x;

    第一次相遇时,fast走的长度 2S = LenA + n*x;

    所以可以知道,LenA + x =  n*R;  LenA = n*R -x;

ListNode* findLoopEntrance(ListNode* pHead<span style="line-height: 20.16px; font-family: Verdana, Arial, Helvetica, sans-serif;">)  </span>
{  
    ListNode* fast = pHead;
    ListNode* slow = pHead;  
    while( fast != NULL && fast->next != NULL)  
    {  
  
        fast = fast->next->next;  
        slow = slow->next;  
        //如果有环,则fast会超过slow一圈  
        if(fast == slow) //跳出循环时处于第一次相遇位置 
        {  
            break;  
        }  
    }  
    if(fast == NULL || fast->next == NULL)  //不是环
        return NULL;  
    slow = pHead;  //让慢指针指向头结点,两者下次相遇时就是环入口点位置
    while(slow != fast)  
    {  
        slow = slow->next;  //各走一步
        fast = fast->next;  
    }  
  
    return slow;  
}  
扩展问题:

判断两个单链表是否相交,如果相交,给出相交的第一个点(两个链表都不存在环)。

比较好的方法有两个:

一、将其中一个链表首尾相连,检测另外一个链表是否存在环,如果存在,则两个链表相交,而检测出来的依赖环入口即为相交的第一个点。

二、如果两个链表相交,那个两个链表从相交点到链表结束都是相同的节点,我们可以先遍历一个链表,直到尾部,再遍历另外一个链表,如果也可以走到同样的结尾点,则两个链表相交。---推荐

这时我们记下两个链表length,再遍历一次,长链表节点先出发前进(lengthMax-lengthMin)步,之后两个链表同时前进,每次一步,相遇的第一点即为两个链表相交的第一个点。---找交点
int isCross(node *h1, node *h2)
{
    node *p, *q;
    
    for(p = h1; p->next!=NULL; p++);    
    for(q = h2; q->next!=NULL; q++);
    
    return p == q ? 1 : 0; 
}












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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值