11.判断单链表是否带环?若带环,求环的长度?求环的入口点?并计算时间复杂度和空间复杂度
①判断链表是否带环
思路:还是利用快慢指针法,快指针一次走两步,慢指针一次走一步。如果有环那么两者最后必然相遇,反之便是不带环。
思考:如果快指针一次走三步、四步或者k步(k>2),慢指针一次走一步,在带环的情况下他两最后能相遇吗?
![]()
当慢指针刚进入环时,快指针已经走到图中位置,现在他们之间的路程是X,我们设快指针速度为Vf,慢指针速度为Vs,从慢指针进入环到两者相遇,要经过时间T。
T = X / (Vf - Vs)
我们知道这里的X 和 T都是整数,当Vf - Vs = 1时这个式子是恒成立的;
如果Vf - Vs = 2时,只有X为2的倍数时才成立
如果Vf - Vs = 3时,只有X为3的倍数时才成立
如果Vf - Vs = k时,只有X为K的倍数时才成立我们知道X是不确定的,是一个随机正整数,只有Vf - Vs = 1的条件下才能确保快慢指针最后会在环内相遇。
SListNode* HasCircle(SListNode* list) //判断链表是否带环
{
/*时间复杂度O(n)*/
assert(list != NULL);
SListNode* pFast = list;
SListNode* pSlow = list;
while (pFast != NULL && pFast->_next != NULL)
{
pFast = pFast->_next->_next;
pSlow = pSlow->_next;
if (pFast == pSlow)
{
return pFast;
}
}
return NULL;
}
②求环的长度
思路:上一个程序会返回快慢指针第一次在环内相遇的节点位置,这个节点是 pMeetNode ,我们把这个节点记下来。
让一个指针cur从这个节点开始走,绕环一圈,它每走一步计数器count++,当cur再次走到pMeetNode这里时,count的值即为环的长度。
size_t GetCircleLenth(SListNode* pMeetNode) //判断环的长度
{
/*时间复杂度O(n)*/
assert(pMeetNode != NULL); //pMeetNode是 pFast和pSlow 第一次相遇的节点
size_t count = 1;
SListNode* cur = pMeetNode->_next;
while (cur != pMeetNode)
{
cur = cur->_next;
count++;
}
return count;
}
③找环的入口
思路:这题有一个数学结论
在快慢指针第一次相遇的地点放一个指针pMeetNode,在头结点处放一个指针cur,然后两者同时走(都是每次走一步),两个指针最后会在环的入口处相遇。如图,我们设链表头结点到环的入口距离为X
环的入口到快慢指针第一次相遇的节点pMeetNode的距离为D
环的周长为Y我们先求出第一问中,当快慢指针第一次在环内相遇时,两者走过的路程。
快指针路程 F=X+nY+D F = X + n Y + D ; //n是两指针相遇时,快指针绕环走过的圈数
慢指针路程 S=X+D S = X + D ;
因为前者速度是后者两倍
所以: F=2×S F = 2 × S
即: X+nY+D=2×(X+D) X + n Y + D = 2 × ( X + D )
则: nY=X+D n Y = X + D
那: nY−D=X n Y − D = X
(n−1)Y+Y−D=x ( n − 1 ) Y + Y − D = x
最后得出的结论是nY - D = X。我们分析分析这个式子:
nY - D 代表的是 pMeetNode到环入口的距离
X代表的是头结点到环入口的距离。
这个式子最后推出这两者是相等的。
所以上面那个解题结论就是这么推到过来的。
SListNode* GetEntranceNode(SListNode* list, SListNode* pMeetNode) //找出环的入口
{
/* 指针 pMeetNode, 和表头指针cur
*两个指针同时往后走,相遇点便是环的入口*/
assert(list != NULL && pMeetNode != NULL);
SListNode* cur = list;
while (cur != pMeetNode)
{
cur = cur->_next;
pMeetNode = pMeetNode->_next;
}
return cur;
}
12.判断两个链表是否相交,若相交,求交点。(假设链表不带环)
思路(判断相交):这里两个链表都是不带环的,所有我们可以找这两条链表的尾节点,如果两者的尾节点是同一个节点,那这两条链表就是相交的。
int SListIsCrossNode(SListNode* list1, SListNode* list2) //判断两链表是否相交
{
assert(list1 != NULL);
assert(list2 != NULL);
SListNode* move1 = list1;
SListNode* move2 = list2;
while (move1->_next != NULL)
{
move1 = move1->_next;
}
while (move2->_next != NULL)
{
move2 = move2->_next;
}
if (move1 == move2)//相交返回1
{
return 1;
}
else
{
return 0;
}
}
思路(找相交节点):假设两个链表长度一样,我们可以让两者从头结点开始同时往后走,每走一步判断两者是否相等,如果相等那这个相等的节点便是相交点;
但现在这两条链表的长度不一定相等,我们要手动转换成相等的情况来做。
求出这两条链表的长度的差值K,让长的链表先走K步,然后两者再同时走,这样走到两者相等时,就找到了中间节点。
int GetListLenth(SListNode* list) //求链表长度
{
SListNode* cur = list;
int lenth = 0;
while (cur != NULL)
{
lenth++;
cur = cur->_next;
}
return lenth;
}
SListNode* MoveToCrossNode(SListNode* LongList, SListNode* ShortList, size_t gap) //移动到相交点
{
while (gap--)
{
LongList = LongList->_next;
}
while (LongList != NULL)
{
LongList = LongList->_next;
ShortList = ShortList->_next;
if (ShortList == LongList)
{
return ShortList;
}
}
return NULL;
}
SListNode* GetCrossNode(SListNode* list1, SListNode* list2)
{
assert(list1 != NULL);
assert(list2 != NULL);
int k = GetListLenth(list1) - GetListLenth(list2);
SListNode* CrossNode = NULL;
if (k > 0) //list1比list2长
{
CrossNode = MoveToCrossNode(list1, list2, abs(k));
}
if (k < 0) //list2比list1长
{
CrossNode = MoveToCrossNode(list2, list1, abs(k));
}
else //一样长
{
CrossNode = MoveToCrossNode(list1, list2, abs(k));
}
return CrossNode;
}
13.判断两个链表是否相交,若相交,求交点。(假设链表可能带环)【升级版】
思路: 这里分三种情况讨论
(1)都不带环,转换成12题。
(2)一个带环,一个不带环。那一定不相交。
(3)都带环。都带环也分为以下三种情况(利用两个相遇点(M1,M2) 判断是否带环——让M1不动,M2走一圈,如果两个能相遇,那就相交,反之不相交)
①不相交
②环外相交(入口点相同)——从入口点处把链表截断,问题就转换成求两个不带环链表相交点。
③环内相交(入口点不同)
int SListRingIsCrossNode(SListNode* list1, SListNode* list2)
{
assert(list1 != NULL);
assert(list2 != NULL);
SListNode* pMeetNode1 = HasCircle(list1);//HasCircle是上一题创建的函数
SListNode* pMeetNode2 = HasCircle(list2);
SListNode* entry1 = NULL;
SListNode* entry2 = NULL;
if (pMeetNode1 == NULL && pMeetNode1 == NULL) // 都不带环的情况
{
return 0;
}
else if ((pMeetNode1 == NULL && pMeetNode2 != NULL) || (pMeetNode1 != NULL && pMeetNode2 == NULL)) //一个带环一个不带环情况
{
return -1; //不相交
}
else //两个都带环情况
{
SListNode* cur = pMeetNode1->_next;
while (cur != pMeetNode1)
{
if (cur == pMeetNode2) //两者相交
{
entry1 = GetEntranceNode(list1, pMeetNode1); //GetEntranceNode是上一题创建的函数
entry2 = GetEntranceNode(list2, pMeetNode2);
if (entry1 == entry2) //环外相交
{
return 1;
}
else //环内相交
{
return 2;
}
}
cur = cur->_next;
}
return -1;//不相交
}
}
SListNode* GetRingCrossNode(SListNode* list1, SListNode* list2) //获取相交点
{
assert(list1 != NULL);
assert(list2 != NULL);
SListNode* CrossNode = NULL;
int result = SListRingIsCrossNode(list1, list2);
if (-1 == result)
{
CrossNode = NULL;
}
else if (0 == result)
{
CrossNode = GetCrossNode(list1, list2); //两链表都不带环
}
else if (1 == result) //环外相交
{
SListNode* entry = GetEntranceNode(list1, HasCircle(list1));
entry->_next = NULL;
CrossNode = GetCrossNode(list1, list2);//GetCrossNode是上一题的函数.
}
return CrossNode;
}
14.复杂链表的复制。一个链表的每个节点,有一个指向next指针指向下一个节点,还有一个random指针指向这个链表中的一个随机节点或者NULL,现在要求实现复制这个链表,返回复制后的新链表。
思路:这里我们把复制过程分为三步:
第一步:复制原链表的每一个节点,把复制的新节点插在原节点的后面。
第二步:处理新节点的random,新节点的random就是原节点的random的下一个节点
第三步:分离两个链表
typedef struct ComplexNode
{
DataType _data;
struct ComplexNode* _next;
struct ComplexNode* _random; //_random指向这个链表中的一个随机节点或者NULL,
}ComplexNode;
ComplexNode* BuyComplexNode(DataType x)
{
ComplexNode* newComplexNode = (ComplexNode*)malloc(sizeof(ComplexNode));
if (newComplexNode != NULL)
{
newComplexNode->_data = x;
newComplexNode->_next = NULL;
newComplexNode->_random = NULL;
}
return newComplexNode;
}
ComplexNode* CopyComplexList(ComplexNode* list)
{
/*拷贝链表*/
ComplexNode* cur = list;
while (cur != NULL)
{
ComplexNode* newComplexNode = BuyComplexNode(cur->_data);
newComplexNode->_next = cur->_next;
cur->_next = newComplexNode;
cur = newComplexNode->_next;
}
/*置random*/
cur = list;
while (cur != NULL)
{
if (cur->_random != NULL)
{
cur->_next->_random = cur->_random->_next;
}
cur = cur->_next->_next;
}
/*拆减链*/
cur = list;
ComplexNode* NewHead = cur->_next;
ComplexNode* next = cur->_next;
ComplexNode* Nextnext = next->_next;
while (cur != NULL)
{
cur->_next = Nextnext;
if (next->_next != NULL)
{
next->_next = Nextnext->_next;
}
cur = Nextnext;
if (cur != NULL)
{
next = cur->_next;
Nextnext = next->_next;
}
}
return NewHead;
}
void ComplexListPrint(ComplexNode* com)//打印链表
{
assert(com != NULL);
ComplexNode* cur = com;
while (cur != NULL)
{
if (cur->_random == NULL)
printf("%d:NULL -> ", cur->_data);
else
printf("%d:%d -> ", cur->_data, cur->_random->_data);
cur = cur->_next;
}
printf("NULL \n");
}
运行结果:
15.求两个已排序单链表中相同的数据。
void UnionSet(SListNode* list1, SListNode* list2)
{
while (list1 != NULL && list2 != NULL)
{
if (list1->_data < list2->_data)
{
list1 = list1->_next;
}
else if (list1->_data > list2->_data)
{
list2 = list2->_next;
}
else
{
printf("%d ", list1->_data);
list1 = list1->_next;
list2 = list2->_next;
}
}
}
运行结果: