有关链表的经典面试题(二)

1.判断单链表是否带环?若带环,求环的长度?求环的入口点?并计算每个算法的时间复杂度&空间复杂度。
思路:利用快慢指针,快指针一次走两步,慢指针一次走一步,如快慢指针有相遇点,则一定有环。找到相遇点后,求环长度问题,可以转换为求头结点到            相遇点之间的长度问题。求入口点时,让快指针回到头结点,两指针再次相遇的点即入口点。
问题一:为什么是一个走一步,一个走两步?可不可以一个走一步一个走三步?
答:假设已经带环,一个跑快点,一个跑慢点,那么 进入环后,快的走两步慢的走一步,每次可以缩短一步的距离,两指针可以相遇。
       而快的一次走3步,一次走四步就不一定了。(取决于慢指针进入环时与快指针的距离。如果只差一步,而快一次走3次的话,无法相遇。)
问题二:为什么求环长度问题,可以转换为求头结点到相遇点之间的长度问题?
答:假设头结点到环的入口点的距离为L,入口点到快慢指针相遇点的距离为x,环的长度为c,则有:

问题三:为什么求环的入口点,可以转换为求让快指针回到头结点后两指针再次相遇的点?
答:假设头结点到环的入口点的距离为L,入口点到快慢指针相遇点的距离为x,环的长度为c,则有:

本题的代码为:
Node* CheckCycle(Node* pHead)  //判断单链表是否带环
{
       Node* fast = pHead;
       Node* slow = pHead;
       while (fast&&fast->next)//快指针走两步,慢指针走一步,如果在同一个环中,一定相遇
       {
              fast = fast->next->next;
              slow = slow->next;
              if (fast == slow)
              {
                     return slow;
              }
       }
       return NULL; //没有环(fast可以走到NULL,或fast的下一个为NULL)
}
int LengthofCycle(Node* pHead, Node* pos)  //若带环,求环的长度,pos为相遇点
{
       int length = 0;
       while (pHead != pos&&pHead) //求环长度问题,可以转换为求头结点到相遇点之间的长度问题,因为L+x=nc。  
       {
              pHead = pHead->next;
              ++length;
       }
       return length;
}
Node* FindEntry(Node* pHead)  //求环的入口点
{
       assert(pHead);
       Node* fast = pHead;
       Node*slow = pHead;
       while (fast&&fast->next)  //有环,让快指针追上慢指针
       {
              fast = fast->next->next;
              slow = slow->next;
              if (fast == slow)
              {
                     break;
              }
       }
       fast = pHead;   //再让快指针指向头结点
       while (fast != slow)  //两指针都走一步,当两指针在此相遇时,就是入口点
       {
              fast = fast->next;
              slow = slow->next;
       }
       return fast;
}
 
2.判断两个链表是否相交,若相交,求交点。(假设链表不带环) 
相交的三种情况:Y  V  I  其中I有两种情况,一个是从头就开始相交,一个是第二个链表就包含在公共节点链中。
判断是否相交思路: 可对比两链表的最后一个节点
求交点思路: 1.  两个从头出发,判断两个节点是否相等,不相等的话让某一链表的指针往后移,再对比判断,不相等的话某链表的指针再往后移,如果移到结尾也没有相等,另一个链表的指针后移,再遍历某链表看是否有相等的点。第一个相等点是交点。时间复杂度为:O(n)=T(n2)
                    2.因为相交之后的部分长度是相等的,所以我们先得到长链表长度和短链表长度的差值。然后让长链表从头走过相差的长度,之后再与短链表同时向 后走。若遇到指针相等的点,该交点为两链表的交点。时间复杂度为O(n)=T(m+n)
                    3.让第一个链表l1的尾节点指向另一链表l2的头结点,构成一个环,则两链表的交点成为了环的入口点(整个l2在环内,以l1的头结点来求入口点)。
                    最常用的是用第二种方法。
int IsCrossWithoutCircle(Node* pHead1, Node* pHead2)    //判断两个不带环链表是否相交
{
       Node* tail1 = pHead1;
       Node* tail2 = pHead2;
       if (NULL == pHead1 || NULL == pHead2)
       {
              return 0;
       }
       while (tail1)
       {
              tail1 = tail1->next;
       }
       while (tail2)
       {
              tail2 = tail2->next;
       }
       return tail1 == tail2;
}
Node* GetCrossNode(Node* pHead1, Node* pHead2)    //假设两链表不带换环且相交,求两链表的交点
{
       size_t len1 = 0;
       size_t len2 = 0;
       Node* cur1 = pHead1;
       Node* cur2 = pHead2;
       int div = 0;
       if (NULL == pHead1 || NULL == pHead2)    //两链表不能为空
       {
              return NULL;
       }
       if (!IsCrossWithoutCircle(pHead1, pHead2))    //先判断是否有交点,若没有,直接返回,不用再求交点
       {
              return 0;
       }
       while (cur1)    //遍历链表1,求其长度
       {
              len1++;
              cur1 = cur1->next;
       }
       while (cur2)    //遍历链表2,求其长度
       {
              len2++;
              cur2 = cur2->next;
       }
       div = len1 - len2;   //求长链表与短链表的长度差值
       cur1 = pHead1;
       cur2 = pHead2;
       if (div > 0)
       {
              while (div--)    //当链表1长时,链表1往后走div步
              {
                     cur1 = cur1->next;
              }
       }
       else
       {
              while (div++)   //当链表2长时,链表2往后走div步
              {
                     cur2 = cur2->next;
              }
       }
       while (cur1 != cur2)   //之后两个指针一起走,直到相交
       {
              cur1 = cur1->next;
              cur2 = cur2->next;
       }
       return cur1;
}

测试代码为:
void Testlist6()
{
       Node* list1 = NULL;
       Node* list2 = NULL;
       Node* pos1 = NULL;
       Node* pos2 = NULL;
       PushBack(&list1, 1);
       PushBack(&list1, 2);
       PushBack(&list1, 3);
       PushBack(&list1, 4);
       PushBack(&list1, 5);
       PushBack(&list1, 6);
       PrintList(list1);    //l1= 1 2 3 4 5 6
       PushBack(&list2, 7);
       PushBack(&list2, 8);
       PushBack(&list2, 9);
       pos1 = Find(list1, 4);
       pos2 = Find(list2, 9);
       pos2->next = pos1;
       PrintList(list2);    //l2= 7 8 9 4 5 6
       printf("%d\n", GetCrossNode(list1, list2)->data);
}

3.判断两个链表是否相交,若相交,求交点(假设链表可能带环)
假设两个链表分别为l1和l2,根据两个链表的带环情况来看相交的话,可能会出现的情况有以下几种:

判断是否相交思路: 因为两个链表都带环且相交时会共用一个环,所以在判断相交与否时,可以找到两个链表的相遇点(判断每个链表是否带环时快慢指针相遇的点),然后从任意一个相遇点出发绕环一圈,若在此期间遇到了另一个相遇点,则两个带环链表相交。
求相交点思路: 对于环外相交,可以以任意一个链表的相遇点为尾,分别求两个链表从头结点到该节点的距离,并让长链表先走两者长度差的距离,然后两链表再一起出发,直到遇到相等的节点,该节点为交点。
                       对于环内相交,其实整个换上都是两链表的交点。两个较为特殊的点是两个链表的入口点,可任求一个做交点。
int IsCrossWithCircle(Node* pHead1, Node* pHead2)  //判断链表可能带环时是否相交
{
       Node* meet1 = CheckCycle(pHead1);  //判断链表是否带环
       Node* meet2 = CheckCycle(pHead2);
       if (NULL == meet1&&NULL == meet2)  //如果都不带环,就转化为之前的问题了
       {
              IsCrossWithoutCircle(pHead1, pHead2);
       }
       else if(meet1 && meet2)  //两链表都带环的情况
       {
              Node* cur = meet1;  //记录链表1的相遇点
              while (cur->next != meet1)    //从meet1绕环一圈
              {
                     if (cur == meet2)    //当遇到链表2的相遇点时停止
                     {
                           return 2;
                     }
                     cur = cur->next;
              }
              if (cur == meet2)    //出循环时少判断了一个节点,这里补上
              {
                     return 2;
              }
       }
       return 0;
}



猜你喜欢

转载自blog.csdn.net/gunqu_d/article/details/78254261