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;
}