相交链表~环形链表

发布于:2022-11-06 ⋅ 阅读:(464) ⋅ 点赞:(0)

前言

我们在解决环形链表问题之前呢,先做一道简单题做做铺垫;
环形链表的方法很简单,但是想要弄懂其中的原理,就不得不花费一番力气!!!😊😊😊接下咱就和大家一起来探讨一下这个问题!!!;

相交链表

题目描述:
在这里插入图片描述
➡️ 挑战链接 ⬅️
分析:

首先我们最容易想到的就是暴力破解,将一个链表的节点地址与另一个链表的每一个节点的地址进行比较,看看是否存在相同节点的情况,如果存在,则说明,两个链表存在相交;如果两个链表都遍历完了都没找到相同地址的节点,则说明两个链表无相交的情况出现;

时间复杂度:O(N^2)
空间复杂度:O(1)
代码实现:

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
                 struct ListNode *curA=headA;
                 struct ListNode *curB=headB;
                 for(curA=headA;curA;curA=curA->next)
                 {
                     for(curB=headB;curB;curB=curB->next)
                     if(curB==curA)
                     return curA;
                 }
                 return NULL;
}

在这里插入图片描述

我们可以看到时间效率并不高,该算法还可以进一步改进;
方法2:
在这里插入图片描述

假设题目给定的两个链表一样长的话,我们就可以比较curA和curB的地址值,看看所指节点的地址是否一样,如果不一样则curA与curB同时向后走;
如果最后走到NULL都没找到的话,那么就说明两个链表不存在相交;相反如果存在curA等于curB则说明存在相交,直接返回curA或curB;
但是:
现在前提是我们不能保证两个链表是否一样长,则就需要我们去统计两个链表的长度了,然后让长链表先走长度差的绝对值步;这是就能保住curA和curB是一样长的了,就可以利用上面的思路了:
画图:
在这里插入图片描述

 size_t CountList(struct ListNode*head)
 {
     struct ListNode*cur=head;
     int len=0;
     while(cur)
     {
         len++;
         cur=cur->next;
     }
     return len;
 }
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
                 struct ListNode *cur1=headA;
                 struct ListNode *cur2=headB;
                 int len1=CountList(headA);
                 int len2=CountList(headB);
                 int k=0;
                 if(len2>len1)
                 {
                     k=len2-len1;
                     while(k--)
                     cur2=cur2->next;
                 }
                 else
                 {
                     k=len1-len2;
                     while(k--)
                     cur1=cur1->next;
                 }
                 while(cur2)
                 {
                     if(cur1!=cur2)
                     {
                         cur1=cur1->next;
                         cur2=cur2->next;
                     }
                     else
                     return cur1;
                 }
                 return NULL;
}

时间复杂度:O(N);
空间复杂度:O(1);

在这里插入图片描述
代码改良版:

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
          struct ListNode *curA=headA;
          struct ListNode *curB=headB;
          int lenA=0;
          int lenB=0;
          //1、先判断一下每个链表的最后一个节点是否相等,如果最后一个节点都不想等,说明两个链表一定没有相交;反之,则一定相交了;
          while(curA->next)
          {
              curA=curA->next;
              lenA++;
          }
          while(curB->next)
          {
              curB=curB->next;
              lenB++;
          }
          if(curB!=curA)//两链表最后一节点比较
          return NULL;
          //以上没有相交的链表都没拦截于此
          //接下来就是处理一定相交的链表,还是上面代码的思路;
          struct ListNode *LongList=headA;
          struct ListNode *ShortList=headB;
          if(lenB>lenA)
          {
              LongList=headB;
              ShortList=headA;
          }
           int gap=abs(lenB-lenA);//两链表的节点数目差值
           //先让长链表先走gap步
           while(gap--)
           {
               LongList=LongList->next;
           }
           //两个链表保持一致长了
           while(LongList)
           {
               if(LongList==ShortList)
               break;
               LongList=LongList->next;
               ShortList=ShortList->next;
           }
          return ShortList;
} 

在这里插入图片描述

环形链表Ⅰ

前面铺垫做完了,我们再来慢慢进入正题:
题目描述:
在这里插入图片描述
➡️ 挑战链接 ⬅️
分析:

首先解决这道题目的方法就是快慢指针,fast指针每次走2步,slow指针每次走1步;
如果有环的话,fast指针一定会等于slow指针(why?我们下文解释);
如果没环的话,fast最后一定是等于NULL或者fast->next==NULL;


偶数节点,无环情况,结束条件
在这里插入图片描述
奇数节点,无环情况,结束条件

时间复杂度:O(N)
空间复杂度:O(1)

代码实现:

bool hasCycle(struct ListNode *head) {
         struct ListNode *fast=head;
         struct ListNode *slow=head;
         while(fast&&fast->next)
         {
             fast=fast->next->next;
             slow=slow->next;
             if(slow==fast)//说明带环
             return true;
         }
         return false;
}

在这里插入图片描述
代码和思路就是这么简洁
下面我们来解释一下:为什么快指针每次走两步,慢指针走一步可以?快指针可不可以每次走3步?

假设链表带环
首先我们清楚一定是快指针先进入圆环,对吧!
其次呢,当我slow指针刚好在圆环进入点的时候,fast指针也存在与圆环中的某个位置,如图:
在这里插入图片描述
同时呢,我们假设fast和slow指针的相对步长呢为N步(顺时针方向的步长),现在我们fast指针是每次走两步,slow指针是每次走一步,那么我们就走呗:
在这里插入图片描述
同时我们可以知道fast和slow指针没变换一次,他们之间的距离就会减少1步;
why?
我们可以这样理解,假设只有fast动,那么fast和slow之间的距离是不是就是N-2;
但是现在加上slow动的距离,fast和slow之间的距离是不是又被拉大了,变为N-2+1,即N-1;
后面以此类推……
那么fast与slow之间的距离变化就是:
N
N-1
N-2
N-3
……
0//
fast和slow之间距离最后会减小为0;
当N0了就说明fast与slow并肩同行了,也就是说fastslow了;
这样解释了,为什么fast每次走2步,slow每次走1步,最会一定会在环里面并肩
那么我们再来看看,fast每次走3步,slow每次走1步行不行嘞?
还是刚才的思路,fast与slow的初始距离为N:
在这里插入图片描述
那么下一次的距离就变为了N-2;
假设只有fast动,距离就是N-3,加上slow移动距离,使得slow与fast之间距离变大,N-3+1,即N-2;
后面依次类推……
N的变化规律:
N
N-2
N-4
……
假设N是偶数的话,那么N最后一定会减为0,那么fast一定能与slow并肩,也就一定能有fast==slow;
假设N是奇数的话,那么N最后一定会为-1,这个-1代表什么意思?
就是说fast与slow擦肩而过,fast刚好领先slow一步,这时我们设圆环周长为C,
那么fast和slow之间的距离(fast到slow,顺时针)就为C-1;
在这里插入图片描述
这不有回到上一层?
令T=C-1
T的变化:
T
T-2
T-4
……
假设T为偶数的话,那么slow与fast指针一定能并肩;
假设T为奇数的话,那么fast与slow永远不可能并肩了;
why?
T都为奇数了,那么是不是最后又会回到T=-1的时候?是不是又回到了上图表示的情形,fast与slow之间的距离又是T(C-1)然后就这样不断循环,所以fast永远不可能并肩;
这也就是我们为什么会让fast走2步,slow走1步的原因;(当然只要fast与slow之间的速度差等于1,理论上来说都是可以并肩的)
————————————————————————————————————————————————
同时,如果我们悬着fast每次走2步,slow每次走一步的情况的话,slow和fast最多在一圈内完成并肩;
我们再来证明这个理论:
在这里插入图片描述

好了,现在我们知道了这些原理和解释,我们来给环形链表加点难度;

环形链表Ⅱ

题目描述:
在这里插入图片描述
➡️挑战链接⬅️
分析:
通过上面的题我们知道了如何判断一个链表是不是环形链表,那么我们的对于上面的码,我们可以稍加处理:

struct ListNode *detectCycle(struct ListNode *head) {
     struct ListNode *fast=head;
       struct ListNode *slow=head;
         while(fast&&fast->next)
         {
             fast=fast->next->next;
             slow=slow->next;
             if(slow==fast)//说明带环
             {
             //核心部分
             }
         }
         return NULL;//链表无环
}

这时问题就变为了,如何计算带环链表的进环入口,
首先我先说结论;
**加粗样式**
我们通过上文的证明,我们知道如果fast每次走2步,slow每次走一步,一定会在环内相遇,
那么我们吧相遇点记为meet,然后将链表头节点记为plist,然后让plist和meet每次都走1步的速度往后走,最后一定会存在meet等于plist的情况,那么那个相等的点就是入口点!!;
代码实现:

struct ListNode *detectCycle(struct ListNode *head) {
     struct ListNode *fast=head;
       struct ListNode *slow=head;
         while(fast&&fast->next)
         {
             fast=fast->next->next;
             slow=slow->next;
             if(slow==fast)//说明带环
             {
             //核心部分
             struct ListNode *meet=slow;
             struct ListNode *plist=head;
             while(plist!=meet)
             {
                 meet=meet->next;
                 plist=plist->next;
             }
             return plist;
             }
         }
         return NULL;//链表无环
}

我们现在来解释一下,为什么meet与plist为什么一定会相遇,并且一定相遇与入口点:
在这里插入图片描述
当然前面我们证明了,slow与fast一定在一圈以内完成并肩,因此slow的距离是L+N;

如果上面寻找入口节点的方法不明白,还有个更简单的办法,就是将meet位置节点剪掉,同时将slow->next置空,那么环形链表是不是就变为了相交链表,而入口点不就是第一个相交点嘛:
于是我们当一把CV工程师:
代码实现:

 struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
          struct ListNode *curA=headA;
          struct ListNode *curB=headB;
          int lenA=0;
          int lenB=0;
          while(curA->next)
          {
              curA=curA->next;
              lenA++;
          }
          while(curB->next)
          {
              curB=curB->next;
              lenB++;
          }
          if(curB!=curA)
          return NULL;
          struct ListNode *LongList=headA;
          struct ListNode *ShortList=headB;
          if(lenB>lenA)
          {
              LongList=headB;
              ShortList=headA;
          }
           int gap=abs(lenB-lenA);
           while(gap--)
           {
               LongList=LongList->next;
           }
           while(LongList)
           {
               if(LongList==ShortList)
               break;
               LongList=LongList->next;
               ShortList=ShortList->next;
           }
          return ShortList;
} 
struct ListNode *detectCycle(struct ListNode *head) {
     struct ListNode *fast=head;
       struct ListNode *slow=head;
         while(fast&&fast->next)
         {
             fast=fast->next->next;
             slow=slow->next;
             if(slow==fast)//说明带环
             {
             //核心部分
             struct ListNode *plist=head;
             struct ListNode *meet=slow->next;
             slow->next=NULL;
                 return getIntersectionNode(plist,meet);
             }
         }
         return NULL;//链表无环
}

时间复杂度:O(N)
空间复杂度:O(1)
在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到