链表快慢指针应用

链表中倒数第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; 
}












### 快慢指针算法在链表中的应用场景及实现 快慢指针是一种常见的双指针技巧,在处理链表问题时非常有效。它通过两个速度不同的指针遍历链表来解决问题,能够显著降低时间复杂度并优化空间需求。 #### 应用场景分析 1. **检测环形链表** 使用快慢指针可以判断单向链表是否存在环结构。如果存在环,则快指针最终会追上慢指针;如果没有环,则快指针会先到达链表末尾[^1]。 2. **寻找环的入口节点** 当确认链表有环后,可以通过调整其中一个指针重新指向头结点的方式找到环的具体入口位置[^2]。 3. **查找中间节点** 利用快慢指针可以在一次遍历中定位到链表的中间节点。当快指针移动两步而慢指针只前进一步时,慢指针所处的位置即为链表中心。 4. **获取倒数第 K 个节点** 设置快指针先行走 K 步之后再启动慢指针同步前进,这样当快指针抵达终点时,慢指针正好处于目标位置之前的一个节点上。 #### 实现代码示例 以下是几个典型例子及其对应的 C++ 或 Python 的解决方案: ##### 检测是否有环 ```cpp ListNode* hasCycle(ListNode* head) { if (!head || !head->next) return nullptr; ListNode *slow = head, *fast = head; while (fast && fast->next){ slow = slow->next; // 移动一步 fast = fast->next->next; // 移动两步 if(slow == fast){ // 如果相遇则说明有环 return true; } } return false; // 遍历结束无环返回false } ``` ##### 找到环的起始点 ```python def detectCycle(head): if not head or not head.next: return None slow, fast = head, head # 查看是否成环 while fast and fast.next: slow = slow.next # 慢指针每次向前一格 fast = fast.next.next # 快指针每次跳过两格 if slow == fast: # 发现有交集 break # 跳出循环准备找入环口 else: # 若未发现任何交叉情况退出while loop return None # 表明不存在闭环直接return none即可 # 将其中任意一个重置回起点再次同时出发直到两者再度交汇于入环处为止 slow = head # 让慢指针回到头部开始新一轮迭代过程 # 这次保持相同步伐大小逐步靠近直至碰面那一刻就是我们要找的那个特殊地址啦! while slow != fast: slow = slow.next # 均匀增量推进至相逢之地~ fast = fast.next return slow # 返回该共同坐标作为结果输出。 ``` ##### 获取链表中间节点 ```java public class MiddleNodeFinder { public static ListNode findMiddle(ListNode head) { if (head == null || head.next == null) { return head; } ListNode slow = head; ListNode fast = head; while(fast != null && fast.next != null){ slow = slow.next; // Move one step at a time. fast = fast.next.next; // Move two steps ahead. /* When 'fast' reaches the end of list, 'slow' will be pointing to middle node */ } return slow; } } ``` ##### 寻找倒数第K个节点 ```javascript function getKthFromEnd(head, k) { let fast = head, slow = head; for(let i=0;i<k;i++) { // Advance `fast` pointer by 'k' positions first. if(!fast) return null; fast = fast.next; } while(fast !== null){ // Then move both pointers until 'fast' hits the tail. fast = fast.next; slow = slow.next; } return slow; // Now 'slow' points exactly where we need it - k-th from last element ! } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值