上篇博客介绍了查询链表中间结点,删除非尾结点,逆序链表三个问题。
有兴趣可点击链接查看:单链表相关面试题(一)
此篇博客将介绍:
1.删除倒数第k个结点(k>1)
2.合并两个有序链表
3.判断链表是否带环,若带环求环的长度,找出环的入口点
4.判断两条链表是否相交,相交则求出交点。
一、删除倒数第k个结点
1.算法思路:
仍然通过设置快慢指针的方法解决,快指针每走一步,让k--,当k<0的时候,让慢指针也开始走,这样,当快指针走到链表尾部的时候,慢指针刚好指向倒数第k个结点。最后删除慢指针指向的那个结点。简单来说就是,让快指针先走k部,慢指针再开始走,这样快慢指针之间总是相差k步,所以,快指针指向空时,慢指针离空k个结点,即慢指针指向倒数第k个结点。
2.代码实现:
void DelKNode(pLinkList pList, int k)
{
assert(pList);
assert(k > 1);
if (pList->pHead == NULL)
{
return;
}
pLinkNode fast = pList->pHead;
pLinkNode slow = pList->pHead;
while (fast)
{
k--;
if (k < 0)
{
slow = slow->next;
}
fast = fast->next;
}
if (k<=0) //为了防止给进去的k不符合要求,超过链表的总长
{
pLinkNode del = NULL;
del = slow->next;
slow->data = del->data;
slow->next = del->next;
free(del);
del = NULL;
}
}
3.测试
test9()
{
LinkList list;
InitLinkList(&list);
PushBack(&list, 1);
PushBack(&list, 2);
PushBack(&list, 3);
PushBack(&list, 4);
PushBack(&list, 5);
PushBack(&list, 6); //构建链表1->2->3->4->5->6
PrintList(&list);
DelKNode(&list, 5); //删除倒数第5个结点
PrintList(&list);
}
4.测试结果
二、合并两个有序链表
1.算法思路: 假设顺序是由小到大,先考虑异常情况,当其中一条链表为空则返回另外一条链表。当两条链表都不为空的时候,设置新的头结点,比较两条链表第一个结点的数据域大小,让新的头结点指向数据域小的结点,且用来遍历此条链表的结点指针向后移。接下来循环比较两条链表里的数据域,让新链表的尾结点的指针域指向较小的结点。直到其中一条链表指向空,则让新的链表的尾指针的指针域指向用来另外一条链表的当前指针。可能文字描述不太能懂,我们画图说明一下。
2.代码实现:
pLinkNode merge(pLinkNode list1, pLinkNode list2)
{
pLinkNode Newhead = NULL;
pLinkNode Tail = NULL;
pLinkNode cur1 = list1;
pLinkNode cur2 = list2;
if (list1 == NULL)
{
return list2;
}
else if (list2 == NULL)
{
return list2;
}
else
{
if (cur1->data > cur2->data)
{
Newhead = cur2;
cur2 = cur2->next;
}
else
{
Newhead = cur1;
cur1 = cur1->next;
}
Tail = Newhead;
while (cur1&&cur2)
{
if (cur1->data > cur2->data)
{
Tail->next = cur2;
cur2 = cur2->next;
Tail = Tail->next;
}
else
{
Tail->next = cur1;
cur1 = cur1->next;
Tail = Tail->next;
}
}
if (cur1)
{
Tail->next = cur1;
}
else if (cur2)
{
Tail->next = cur2;
}
}
return Newhead;
}
3.测试
test10()
{
LinkList list; //构建两个链表
LinkList list1;
LinkList list2;
pLinkNode ret = NULL;
pLinkNode l1 = NULL;
pLinkNode l2 = NULL;
InitLinkList(&list);
InitLinkList(&list1);
InitLinkList(&list2);
PushBack(&list, 1);
PushBack(&list, 3);
PushBack(&list, 5);
PushBack(&list, 7);
PushBack(&list1, 2);
PushBack(&list1, 4);
PushBack(&list1, 6);
PrintList(&list);
PrintList(&list1);
l1 = list.pHead;
l2 = list1.pHead;
list2.pHead= merge(l1, l2);//接收新的头结点
PrintList(&list2);
DestroyList(&list);
DestroyList(&list1);
DestroyList(&list2);
}
4.测试结果
三、判断链表是否带环,若带环求环的长度,找出环的入口点
1.算法思路: 设置快慢指针,快指针每次走两步,慢指针每次走一步,若链表带环,则快慢指针一定会在环上相遇,我们返回相遇点,若不带环,则快指针或者其指针域会为空,此时我们返回空。
如何计算环的长度:从相遇点遍历一遍,通过计数器获得环的长度。
找环的入口点:
2.代码实现:
pLinkNode CheckCycle(pLinkList pList); //判断链表是否带环
int GetCircleLength(pLinkNode meet); //若带环求环的长度
pLinkNode FindEntryNode(pLinkList pList, pLinkNode meet);//找出环的入口点
pLinkNode CheckCycle(pLinkList pList)
{
assert(pList);
pLinkNode fast = pList->pHead;
pLinkNode slow = pList->pHead;
while(fast&&fast->next)
{
slow = slow->next;
fast = fast->next->next;
if (fast == slow)
{
return slow;
}
}
return NULL;
}
int GetCircleLength(pLinkNode meet)
{
assert(meet);
int count = 1;
pLinkNode cur = meet->next;
while (cur != meet)
{
count++;
cur = cur->next;
}
return count;
}
pLinkNode FindEntryNode(pLinkList pList, pLinkNode meet)
{
assert(pList);
pLinkNode cur = pList->pHead;
while (cur != meet)
{
cur = cur->next;
meet = meet->next;
}
return cur;
}
3.测试
void test11()
{
LinkList list;
int length=0;
pLinkNode ret = NULL;
pLinkNode meet = NULL;
pLinkNode entry= NULL;
InitLinkList(&list);
PushBack(&list, 1);
PushBack(&list, 2);
PushBack(&list, 3);
PushBack(&list, 4);
PushBack(&list, 5);
PushBack(&list, 6);
ret = Find(&list, 6); //构造环
ret->next = Find(&list, 3); //尾指针域指向3,则环的入口点应该为3
meet=CheckCycle(&list);
if (ret = NULL)
{
printf("链表不带环");
}
else
{
length = GetCircleLength(meet);
printf("环长%d\n", length);
entry = FindEntryNode(&list, meet);
printf("入口点为:%d", entry->data);
}
DestroyList(&list);
}
4.测试结果
四、判断两条链表是否相交,相交则求出交点。
1.算法思路: 链表是否相交分为以下几种情况来讨论:
1>链表不带环,不带环时相交,只要遍历两条链表,判断两条链表的尾结点是否相同,相同则说明相交,不同则不相交。相交时,通过计数器的方法求出两条链表的长度,让长的那条链表先走长度的差值个结点,然后指向两条链表指针同时向后遍历,指针相等的时候就是交点。
2>一条带环一条不带环,此种情况一定不相交。
3>两条链表都带环。调用之前的判断链表是否带环函数,判断两条链表的相遇点是否在一个环上,若不在,则不相交,在一个环上则说明相交。相交时,从环上的任意一点断开。参照不带环相交的情况求出相交点。
2.代码实现:
pLinkNode CheckCross(pLinkList list1, pLinkList list2)
{
int count1 = 1;
int count2 = 1;
pLinkNode tmp1 = CheckCycle(list1);//调用现有函数检测链表是否带环
pLinkNode tmp2 = CheckCycle(list2);
if (tmp1 == NULL && tmp2 == NULL) //两条链表都不带环
{
pLinkNode cur1 = list1->pHead;
pLinkNode cur2 = list2->pHead;
pLinkNode pcur1 = list1->pHead;
pLinkNode pcur2 = list2->pHead;
while (cur1->next)
{
cur1 = cur1->next;
count1++;
}
while (cur2->next)
{
cur2 = cur2->next;
count2++;
}
if (cur1 == cur2) //链表相交,求交点
{
int ret = count1 - count2;
if (ret > 0)
{
while (ret--)
{
pcur1 = pcur1->next;
}
}
else
{
ret = (int)fabs(ret);
while (ret--)
{
pcur2 = pcur2->next;
}
}
while (pcur1 != pcur2)
{
pcur1 = pcur1->next; //1 2 3 4 5 6 // 2 4 6 8 10 2 3 4 5 6
pcur2 = pcur2->next;
}
return pcur1;
}
else
return NULL;
}
else if (tmp1 != NULL && tmp2 != NULL) //两条链表都带环
{
pLinkNode cur1 = list1->pHead;
pLinkNode cur2 = list2->pHead;
int length1 = GetCircleLength(tmp1);
int length2 = GetCircleLength(tmp2);
if (length1 != length2)
{
return NULL;
}
else
{
pLinkNode pcur1 = list1->pHead;
pLinkNode pcur2 = list2->pHead;
while (--length1)
{
if (tmp1 == tmp2) //链表相交
{
tmp2->next = NULL;
int count1 = 0;
int count2 = 0;
while (cur1)
{
cur1 = cur1->next;
count1++;
}
while (cur2)
{
cur2 = cur2->next;
count2++;
}
int ret = count1 - count2;
if (ret > 0)
{
while (ret--)
{
pcur1 = pcur1->next;
}
while (pcur1 != pcur2)
{
pcur1 = pcur1->next;
pcur2 = pcur2->next;
}
return pcur1;
}
else
{
ret = (int)fabs(ret);
while (ret--)
{
pcur2 = pcur2->next;
}
while (pcur1 != pcur2)
{
pcur1 = pcur1->next;
pcur2 = pcur2->next;
}
return pcur1;
}
break;
}
tmp1 = tmp1->next;
}
if (length1 == 0)
{
return NULL;
}
}
}
return NULL;
}
3.测试
void test12()
{
LinkList list;
LinkList list1;
pLinkNode ret = NULL;
pLinkNode ret1 = NULL;
pLinkNode meet = NULL;
InitLinkList(&list);
InitLinkList(&list1);
PushBack(&list, 1);
PushBack(&list, 2);
PushBack(&list, 3);
PushBack(&list, 4);
PushBack(&list, 5);
PushBack(&list, 6);
PushBack(&list1, 2);
PushBack(&list1, 4);
PushBack(&list1, 6);
PushBack(&list1, 8);
PushBack(&list1, 10);
ret = Find(&list, 6);
ret->next = Find(&list, 3); //构建环
ret1 = Find(&list1, 10);
ret1->next = Find(&list, 2);//设置相交
meet = CheckCross(&list, &list1);
if (meet != NULL)
{
printf("交点是%d", meet->data);
}
else
{
printf("不相交");
}
}
4.测试结果
本文测试用例都调用了链表基础操作里的某些函数。若对此有疑惑的亲可参考博文:
单链表的基本操作