【C语言】单链表相关面试题(二)

上篇博客介绍了查询链表中间结点,删除非尾结点,逆序链表三个问题。

有兴趣可点击链接查看:单链表相关面试题(一)

此篇博客将介绍:

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);<span style="font-family: Arial, Helvetica, sans-serif;">//接收新的头结点</span>
	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.测试结果


本文测试用例都调用了链表基础操作里的某些函数。若对此有疑惑的亲可参考博文: 单链表的基本操作

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值