链表的面试题总结

链表的面试题总结

链表在面试中是非常容易的考点,所以在这里总结一下,希望对大家有所帮助

首先,我们给出链表的基本结构,和基本的操作,创建一个结点,打印链表的结点,尾插法加入结点。

#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
typedef int DataType;
typedef struct SListNode{
	DataType _pData;
	struct SListNode* _pNext;
}SListNode;
SListNode* BuySListNode(DataType x); //
void SListPrint(SListNode* pHead); // 
void SListPushBack(SListNode** ppHead, DataType x); 

SListNode* BuySListNode(DataType x){//创建一个新的结点
	SListNode* cur = (SListNode*)malloc(sizeof(SListNode));
	cur->_pData = x;
	cur->_pNext = NULL;
	return cur;
}
void SListPrint(SListNode* pHead){//打印链表中的结点

	SListNode* cur = pHead;
	if (pHead == NULL)
	{
		printf("List is Full!\n");
		return;
	} 
	else
	{
		while (cur)
		{
			printf("%d ",cur->_pData);
			cur = cur->_pNext;
		}
		printf("\n");
	}
}
void SListPushBack(SListNode** ppHead, DataType x){//尾插法
	SListNode* newNode = BuySListNode(x);
	SListNode* pCur = *ppHead;
	if (pCur == NULL)
	{
		*ppHead = newNode;
	}
	else{
		while (pCur->_pNext!= NULL)
		{
			pCur = pCur->_pNext;
		}
		pCur->_pNext = newNode;
	}
}
int main(){
	SListNode* s = NULL;
	SListPushBack(&s,1);
	SListPushBack(&s,2);
	SListPushBack(&s,3);
	SListPushBack(&s,4);
	SListPushBack(&s,5);
	SListPrint(s);
	return 0;
}
此时我们就创建好一个链表,链表的内容为:1 2 3 4 5 

①从尾到头打印链表

方法中我们定义一个tail指针,每次把tail指针当作结尾的标致向前移动,然后遍历链表打印尾结点

void PrintListTailToHead(SListNode* pHead){
	SListNode* tail = NULL;
	SListNode* cur = pHead;
	while (tail!=pHead)
	{
		cur = pHead;
		while (cur->_pNext!= tail)
		{
			cur = cur->_pNext;
		}
		printf("%d ",cur->_pData);
		tail = cur;
	}
}
②删除无头单链表的非尾结点(不能遍历)

要删除某个结点,首先我们用Find函数查找出这个结点,然后把这个结点的地址返回,供下一次调用。

我们使用替换法删除结点,用next指针标记pos的下一个结点,用next->data覆盖pos->data,然后把next释放,逻辑上pos指的结点就被删除了

SListNode* Find(SListNode* pHead,DataType x){
	SListNode* cur = pHead;
	while (cur)
	{
		if(cur->_pData == x)
			return cur;
		cur = cur->_pNext;
	}
	return NULL;
}
void DeleteNotTailNode(SListNode* pos){	
	SListNode* next = pos->_pNext;
	pos->_pData = next->_pData;
	pos->_pNext = next->_pNext;
	free(next);
}
③在无头单链表的一个结点前插入一个结点(不能遍历)

方法和之前的替换法相当,用后插法在结点的后面插入一个值,用要插入的值改变pos的值,那么在逻辑上就是在pos前面插入一个值

void InsertFrontNode(SListNode* pos,DataType x){
	SListNode* next = pos->_pNext;
	SListNode* newNode = BuySListNode(pos->_pData);
	newNode->_pNext = next;
	pos->_pNext = newNode;
	pos->_pData = x;
}
④约瑟夫环问题

每次按照传入的参数,让cur走相应的步数,然后按照替换法删除在逻辑上删除cur指的结点

SListNode* JosephCircle(SListNode* pHead,int k){
	SListNode* cur = pHead;
	SListNode* next;
	int count = k;
	while (cur->_pNext != cur)
	{
		count = k;
		while (count--)
		{
			cur = cur->_pNext;
		}
		next = cur->_pNext;
		cur->_pData = next->_pData;
		cur->_pNext = next->_pNext;
		free(next);
	}
	return cur;
}
⑤单链表的逆置(不开辟新空间)

方法一:

函数中,我们用n1,n2,n3三个指针联动跑,按照while循环里面的顺序,最后一次出来的时候n2,n3指的是空,n1指的是之前链表的尾部,现在返回n1就是返回新链表的头指针

SListNode* ReverseList(SListNode** ppHead){
	SListNode* n1;
	SListNode* n2;
	SListNode* n3;
	n1 = *ppHead;
	n2 = (*ppHead)->_pNext;
	n3 = n2->_pNext;
	n1->_pNext = NULL;
	while (n2!=NULL)
	{
		n2->_pNext = n1;
		n1 = n2;
		n2 = n3;
		if(n3!=NULL){
			n3 = n3->_pNext;
		}
	}
	return n1;
}
方法二:

SListNode* ReverseList(SListNode** ppHead){
	SListNode* newList = NULL;
	SListNode* cur = *ppHead;
	SListNode* next = cur->_pNext;
	while (cur!=NULL)
	{
		cur->_pNext = newList;
		newList = cur;
		cur = next;
		if(next)
			next = next->_pNext;
	}
	return newList;
}
⑥链表的冒泡排序

void SListBubbleSort(SListNode* pHead){
	SListNode* cur = pHead;
	SListNode* next = cur->_pNext;
	SListNode* tail = NULL;
	DataType temp;
	int flag = 0;
	while (tail!=pHead)
	{
		cur = pHead;
		next = cur->_pNext;
		while (next != tail)
		{
			if(cur->_pData > next->_pData){
				temp = cur->_pData;
				cur->_pData = next->_pData;
				next->_pData = temp;
				flag = 1;
			}
			cur = cur->_pNext;
			next = next->_pNext;
		}
		if(flag == 0){
			return;
		}
		tail = cur;
	}
}
⑦把两个链表合成一个有序链表

注意情况:两个链表有可能不一样长,有可能为空

SListNode* SListMeget(SListNode** list1,SListNode** list2){
	SListNode* tail;
	SListNode* list;
	if(list1 == NULL)//任何一个链表为空,就返回另外一个链表,即使另外一个链表也为空,
		return *list2;
	if(list2 == NULL)
		return *list1;
	if((*list1)->_pData > (*list2)->_pData){//确定第一个值比较小的链表,作为新链表的开始
		list = tail = (*list2);
		(*list2) = (*list2)->_pNext;
	}
	if((*list1)->_pData < (*list2)->_pData){
		list = tail = (*list1);
		(*list1) = (*list1)->_pNext;
	}
	while (*list1 && *list2)//两个链表都没结束的时候,做比较,把新链表的tail->_pNext指向较小的链表
	{
		if((*list1)->_pData < (*list2)->_pData){
			tail->_pNext = *list1;
			(*list1) = (*list1)->_pNext;
		}
		else{
			tail->_pNext = (*list2);
			(*list2) = (*list2)->_pNext;
		}
		tail = tail->_pNext;
	}
	if(*list1){
		tail->_pNext = (*list1);
	}
	if(*list2){
		tail->_pNext = (*list2);
	}
	return list;
}
⑧找到链表的中间链表(只能遍历一遍链表)

函数中,使用一个快指针fast,使用一个慢指针slow,slow走一步,fast走两步,那么在fast走到结尾的时候,slow走了fast的一半,所以slow就走在链表的中间

SListNode* FindMidNode(SListNode* pHead){
	SListNode* fast = pHead;
	SListNode* slow = pHead;
	while (fast  && fast->_pNext)
	{
		fast = fast->_pNext->_pNext;
		slow = slow->_pNext;
	}
	return slow;
}
⑨找到倒数第k个结点(只能遍历一次链表)

方法中首先让快指针和慢指针相差k个位置,那么在快指针到达尾结点的时候,慢指针就在倒数第K个结点

SListNode* FindNodeFromBackOfNumberK(SListNode* pHead,int k){
	SListNode* fast = pHead;
	SListNode* slow = pHead;
	while (k--)
	{
		if(fast){
			fast = fast->_pNext;
		}
	}
	while (fast)
	{
		fast = fast->_pNext;
		slow = slow->_pNext;
	}
	return slow;
}
⑩判断链表是否带环

fast指针一次走两步,slow指针一次走一步,若带环,那么两个指针在环里面一定会相遇

SListNode* IsCircle(SListNode* pHead){
	SListNode* fast = pHead;
	SListNode* slow = pHead;
	while (fast && fast->_pNext)
	{
		fast = fast->_pNext->_pNext;
		slow = slow->_pNext;
		if(fast == slow){
			return fast;
		}
}return NULL;}




⑪判断环的大小,也就是环里面有多少结点

我们使用之前判断是否带环的函数来确定指针在环里面相遇的点,在按照这个点跑一圈,就是这个环的长度。

int CircleLength(SListNode* pHead){
	SListNode* meet = IsCircle(pHead);
	SListNode* cur = meet->_pNext;
	int cnt =1;
	while (cur != meet)
	{
		cnt++;
		cur = cur->_pNext;
	}
	return cnt;

}

⑫判断环的入口点:

SListNode* EnterNode(SListNode* pHead){
	SListNode* cur = pHead;
	SListNode* meet = IsCircle(pHead);
	while (cur != meet)
	{
		cur = cur->_pNext;
		meet = meet->_pNext;
	}
	return cur;
}
判断环的入口点讲解图:

此后我还会继续在文章中补充关于链表的题目,敬请关注!

Tip:限于编者水平,文章有很多不足之处,欢迎指正。

如需转载,请注明出处。




评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值