链表面试题总结

链表面试题总结

    • 一.定义数据结构
    • 二.各题目函数声明
    • 三.函数实现
      • (1).从尾到头打印链表(题目来源:剑指offer)
      • (2).删除一个无头单链表的非尾结点(不能遍历链表)(题目来源:剑指offer)
      • (3).用单链表实现约瑟夫环问题
      • (4).在无头单链表的一个节点前插入一个节点(不能遍历链表)
      • (5).链表的逆置/反转
      • (6).链表的冒泡排序
      • (7).合并两个有序的链表,合并后依然有序
      • (8).合并两个有序的链表,合并后依然有序(递归版本)
      • (9).求单链表中间结点
      • (10).求单链表倒数第K个结点
      • (11).判断单链表是否带环,带环返回相遇点,不带环返回NULL
      • (12).求单链表中环的长度
      • (13).求单链表中求环的入口点
      • (14).判断链表是否相交(假设链表不带环),相交返回1,不想交返回0
      • (15).求两个不带环单链表的交点
      • (16).求两个已排序链表中相同的数据(交集)

一.定义数据结构

typedef int DataType;

typedef struct Node
{
    DataType data;
    struct Node * next;
}Node,*pNode,LinkList,*pLinkList;

二.各题目函数声明

//从尾到头打印链表
void PrintLinkTailToHead(pLinkList pl);

//删除一个无头单链表的非尾结点
void RmoveNodeNotTail(pLinkList *ppl, pNode pos);

//用链表实现约瑟夫环
void JosephCircle(pLinkList *ppl, int sz);

//在无头单链表的一个节点前插入一个节点(不能遍历链表)
void InsertNodeNotHead(pLinkList *ppl, DataType data, pNode pos)

//链表的逆置
void Reverselist(pLinkList *ppl);

//链表的冒泡排序
void BubbleSort(pLinkList *ppl);

//合并两个有序的链表,合并后依然有序
pNode Merge(pLinkList list1, pLinkList list2);

//合并两个有序的链表(递归版本)
pNode Merge_R(pLinkList list1, pLinkList list2);

//求单链表中间结点
pNode FindMidNode(pLinkList plist);

//求单链表倒数第K个结点
pNode FindLastKNode(pLinkList plist, int k);

//判断链表是否带环,带环返回相遇点,不带环返回NULL
pNode IsCircle(pLinkList plist);

//求环的长度
int GetCircleLength(pNode meet);

//求环的入口点
pNode GetCircleEntryNode(pLinkList plist, pNode meet);

//判断链表是否相交(假设链表不带环),相交返回1,不想交返回0
int IsCross(pLinkList plist1, pLinkList plist2);

//求两个链表的交点
pNode GetMeetNode(pLinkList plist1, pLinkList plist2);

//求两个链表中相同的数据(交集)
void UnionSet(pLinkList plist1, pLinkList plist2);

三.函数实现

(1).从尾到头打印链表(题目来源:剑指offer)

https://blog.csdn.net/hansionz/article/details/80905962

void PrintLinkTailToHead(pLinkList pl,stack * s)
{
//(1)
#if 0
	if(pl != NULL)
	{
		if(pl->next != NULL)
		{
			PrintLinkTailToHead(pl->next);
		}
		printf("%d--->",pl->data);
	}
	/*if (pl == NULL)
	{
		return NULL;
	}
	PrintLinkTailToHead(pl->next);
	printf("%d--->", pl->data);*/
#endif
//(2)
#if 0
	pNode tail = NULL;
	while (pl != tail)
	{
		pNode cur = pl;
		while (cur->next != tail)
		{
			cur = cur->next;
		}
		printf("%d--->", cur->data);
		tail = cur;
	}
#endif
//(3)
	Node* cur = pl;
	while (cur != NULL)
	{
		s->data[s->top++] = cur->data;
		cur = cur->next;
	}
	while (s->top >= 0)
	{
		printf("%d--->", s->data[s->top--]);
	}
	printf("over\n");
}

(2).删除一个无头单链表的非尾结点(不能遍历链表)(题目来源:剑指offer)

思路:要删除一个结点,就必须找到要删除结点的前驱。但是不能遍历链表,我们找不到前驱。我们试着改变一下思路,将要删除结点的下一个结点的数据复制给要删除的结点,而要删除的结点就变成了原来要删除结点的下一个结点。这样就很巧妙的删除了结点。
https://blog.csdn.net/hansionz/article/details/80934410

void RmoveNodeNotTail(pLinkList *ppl, pNode pos)
{
	pNode del = NULL;
	assert(ppl != NULL);
	assert(pos->next != NULL);
	assert(*ppl != NULL);
	pos->data = pos->next->data;//复制数据
	del = pos->next;//改变要删除的结点
	pos->next = del->next;//删除下一个结点
	free(del);
	del = NULL;
}

(3).用单链表实现约瑟夫环问题

https://blog.csdn.net/hansionz/article/details/81539863

void JosephCircle(pLinkList *ppl, int k)
{
	assert(ppl != NULL);
	pNode cur = *ppl;
	pNode tmp = *ppl;
	pNode del = NULL;
	//将单链表连成一个换
	while (tmp->next)
	{
		tmp = tmp->next;
	}
	tmp->next = *ppl;
	//如果还有大于1个结点则继续查找
	while (cur != cur->next)
	{
		int count = k;
		//走k-1步,清除一个
		while (--count)
		{
			cur = cur->next;
		}
		//删除当前结点
		del = cur->next;
		printf("删除:%d\n", cur->data);
		cur->data = cur->next->data;
		cur->next = del->next;
		free(del);
		del = NULL;
	}
	printf("剩余:%d\n", cur->data);
}

(4).在无头单链表的一个节点前插入一个节点(不能遍历链表)

思路:既然不能遍历链表,说明找不到前驱,那我们只能先将这个结点插入到它的后边,然后在交换数据。或者,在开辟结点的时候就已前面结点的数据开辟,插入之后再将前面结点的数据域设置为已知数据,从而达到交换的效果。

void InsertNodeNotHead(pLinkList *ppl, DataType data, pNode pos)
{
	pNode newNode = NULL;
	assert(ppl != NULL);
	//(1)
#if 0
	DataType tmp = 0;
	newNode = BuyNode(data);
	newNode->next = pos->next;
	pos->next = newNode;
	tmp = newNode->data;
	newNode->data = pos->data;
	pos->data = tmp;
#endif
	//(2)
	newNode = BuyNode(pos->data);
	newNode->next = pos->next;
	pos->next = newNode;
	pos->data = data;
}

(5).链表的逆置/反转

思路:重新开辟一条链表,然后依次取下原链表的每个结点头插入到新的链表中,新的链表就是逆置的结果

void Reverselist(pLinkList *ppl)
{
	pNode cur = *ppl;
	pNode tmp = NULL;
	pLinkList head = NULL;
	assert(ppl != NULL);
	if (*ppl == NULL || (*ppl)->next == NULL)
	{
		return;
	}
	while (cur)
	{
		tmp = cur->next;//保存下一个结点,否则会丢失链表后边的内容
		cur->next = head;
		head = cur;
		cur = tmp;
	}
	*ppl = head;
}

单独总结于另一边博客:
https://blog.csdn.net/hansionz/article/details/82285384

(6).链表的冒泡排序

void BubbleSort(pLinkList *ppl)
{
	assert(ppl != NULL);
	pNode cur = *ppl;
	pNode tail = NULL;
	while (cur != tail)//确定躺数
	{
		while (cur->next != tail) //确定比较次数
		{
			if (cur->data > cur->next->data)
			{
				DataType tmp = cur->data;
				cur->data = cur->next->data;
				cur->next->data = tmp;
			}
			cur = cur->next;
		}
		tail = cur;
		cur = *ppl;
	}
}

(7).合并两个有序的链表,合并后依然有序

pNode Merge(pLinkList list1, pLinkList list2)
{
	pLinkList newhead = NULL;
	//一个链表为空,另一个不为空
	if (list1 == NULL)
	{
		return list2;
	}
	if (list2 == NULL)
	{
		return list1;
	}
	//两个链表都为空
	if ((list1 == NULL) && (list2 == NULL))
	{
		return NULL;
	}
	//无头结点的单链表,必须先插入一个结点才能将以后的结点操作统一
	if (list1->data < list2->data)
	{
		newhead = list1;
		list1 = list1->next;
	}
	else
	{
		newhead = list2;
		list2 = list2->next;
	}
	//保存链表最后一个结点的位置
	pNode tail = newhead;
	//依次比较尾插
	while ((list1 != NULL) && (list2 != NULL))
	{
		if (list1->data < list2->data)
		{
			tail->next = list1;
			list1 = list1->next;
		}
		else
		{
			tail->next = list2;
			list2 = list2->next;
		}
		tail = tail->next;
	}
	//将剩余的结点插入到新链表中
	if (list1 == NULL)
	{
		tail->next = list2;
	}
	else if (list2 == NULL)
	{
		tail->next = list1;
	}
	return newhead;
}

(8).合并两个有序的链表,合并后依然有序(递归版本)

pNode Merge_R(pLinkList list1, pLinkList list2)
{
	pLinkList newhead = NULL;
	if (list1 == NULL)
	{
		return list2;
	}
	if (list2 == NULL)
	{
		return list1;
	}
	if ((list1 == NULL) && (list2 == NULL))
	{
		return NULL;
	}
	if (list1->data < list2->data)
	{
		newhead = list1;
		newhead->next = Merge_R(list1->next, list2);
	}
	else
	{
		newhead = list2;
		newhead->next = Merge_R(list1, list2->next);
	}
	return newhead;
}

(9).求单链表中间结点

思路:利用快慢指针思想,快指针fast每次走两步,满指针slow每次走一步。当快指针做到最后一个结点时,慢指针正好指向中间结点。

pNode FindMidNode(pLinkList plist)
{
	pNode slow = plist;
	pNode fast = plist;
	//链表没有结点或者只有一个结点直接返回
	if ((plist == NULL) || (plist->next == NULL))
	{
		return plist;
	}
	//节点数为偶数时判断条件为fast!=NULL,此时中间节点为后面的那个;
	//为奇数时判断条件为fast->next!=NULL.
	while ((fast != NULL) && (fast->next != NULL))
	{
		fast = fast->next->next;
		slow = slow->next;
	}
	return slow;
}

(10).求单链表倒数第K个结点

思路:依然是利用快慢指针的方法,让快指针先走k-1步,然后快指针和慢指针一起出发,当快指针走到最后一个结点时,慢指针指向倒数第k个结点。

pNode FindLastKNode(pLinkList plist, int k)
{
	pNode fast = plist;
	pNode slow = plist;
	if (plist == NULL)
	{
		return plist;
	}
	//快指针走k-1步
	while (--k)
	{
		//如果链表总的节点数小于k,则肯定不存在倒数第k个结点
		if (fast == NULL)
		{
			return NULL;
		}
		fast = fast->next;
	}
	//快慢指针一起走,快的到终点,慢指针则指向倒数第k个结点
	while (fast->next != NULL)
	{
		fast = fast->next;
		slow = slow->next;
	}
	return slow;
}

(11).判断单链表是否带环,带环返回相遇点,不带环返回NULL

思路:依旧是快慢指针的思想,快指针一次走两步,慢指针一次走一步,如果单链表带环,则快指针和慢指针必然相遇。

//带环返回相遇点,不带环返回NULL
pNode IsCircle(pLinkList plist)
{
	pNode fast = plist;
	pNode slow = plist;
	if (plist == NULL)
		return NULL;
	while ((fast != NULL) && (fast->next != NULL))
	{
		fast = fast->next->next;
		slow = slow->next;
		if (fast == slow)
		{
			return fast;
		}
	}
	return NULL;
}

(12).求单链表中环的长度

思路:从相遇点的下一个结点开始便利一遍环,就可以求出来环的长度了。但是len必须是从1开始,否则会少计算相遇点那个结点。

int GetCircleLength(pNode meet)
{
	assert(meet != NULL);
	pNode cur = meet->next;
	int len = 1;
	//len必须从1开始,否则没有算meet那个结点
	while (cur != meet)
	{
		len++;
		cur = cur->next;
	}
	return len;
}

(13).求单链表中求环的入口点

思路:
链表面试题总结_第1张图片

pNode GetCircleEntryNode(pLinkList plist, pNode meet)
{
	pNode cur = plist;
	if (plist == NULL)
	{
		return NULL;
	}
	assert(meet != NULL);
	while (cur != meet)
	{
		cur = cur->next;
		meet = meet->next;
	}
	return cur;
}

(14).判断链表是否相交(假设链表不带环),相交返回1,不想交返回0

链表面试题总结_第2张图片
思路:两条不带环的链表相交最后一个结点必然相同。

int IsCross(pLinkList plist1, pLinkList plist2)
{
	pNode end1 = plist1;
	pNode end2 = plist2;
	if ((plist1 == NULL) || (plist2 == NULL))
	{
		return 0;
	}
	//确定链表一的尾结点
	while (end1->next != NULL)
	{
		end1 = end1->next;
	}
	//确定链表二的尾结点
	while (end2->next != NULL)
	{
		end2 = end2->next;
	}
	return end1 == end2;
}

(15).求两个不带环单链表的交点

链表面试题总结_第3张图片

pNode GetMeetNode(pLinkList plist1, pLinkList plist2)
{
	int len1 = 0;
	int len2 = 0;
	int gap = 0;
	pNode cur1 = plist1;
	pNode cur2 = plist2;
	//求链表1长度
	while (cur1)
	{
		len1++;
		cur1 = cur1->next;
	}
	//求链表2长度
	while (cur2)
	{
		len2++;
		cur2 = cur2->next;
	}
	//求差值
	gap = abs(len1 - len2);

	cur1 = plist1;//长
	cur2 = plist2;//短
	if (len1 < len2)
	{
		cur1 = plist2;
		cur2 = plist1;
	}
	//长链表走gap步
	while (gap--)
	{
		cur1 = cur1->next;
	}
	//一起走
	while (cur1!=cur2)
	{
		cur1 = cur1->next;
		cur2 = cur2->next;
	}
	return cur1;
}

(16).求两个已排序链表中相同的数据(交集)

void UnionSet(pLinkList plist1, pLinkList plist2)
{
	if ((plist1 == NULL) || (plist2 == NULL))
	{
		return;
	}
	while ((plist1 != NULL) && (plist2 != NULL))
	{
		if (plist1->data < plist2->data)
		{
			plist1 = plist1->next;
		}
		else if (plist1->data > plist2->data)
		{
			plist2 = plist2->next;
		}
		else
		{
			printf("%d ", plist1->data);
			plist1 = plist1->next;
			plist2 = plist2->next;
		}
	}
}

你可能感兴趣的:(数据结构与算法)