剑指offer | 链表

链表结构的定义

对于链表的操作,在面试中经常会被提及,因为面试时间内有限,而链表代码的描述以及操作难度适中,所以在面试中被提及到的概率比较高,我将剑指offer上关于链表的操作做了以下总结:
首先,我们先来定义一个链表结构:

/定义一个节点类型

/定义一个节点类型

typedef struct ListNode
{
	int value;
	ListNode *next;
}ListNode;

链表的基本操作

1、插入

//节点插入
void AdToTail(ListNode **phead, int value)
{
	int *New = (int *)malloc(sizeof(int));

	if(*phead == NULL)//首先得判断是不是头插
	{
		*phead = New;
	}
	else
	{
		ListNode *p = *phead;
		while(p->next != NULL)
		{
			p = p->next;
		}

		p->next = New;
	}
}

2、按值删除

void RemoveNode(ListNode **phead, int value)//删除
{
	if(phead == NULL || *phead == NULL)
	{
		return;
	}

	ListNode *DeleteNode = NULL;
	if((*phead)->value == value)
	{
		DeleteNode = *phead;
		*phead = (*phead)->next;
	}
	else
	{
		ListNode *Node = *phead;
		while(Node->next != NULL && Node->next->value != value)
		{
			Node = Node->next;
		}
		if(Node->next != NULL && Node->next->value == value)
		{
			DeleteNode = Node->next;
			Node->next = Node->next->next;
		}
	}

	free(DeleteNode);
	DeleteNode = NULL;
}

链表之寻找倒数第K个节点(22题)

当大家看到这个题目的时候,首先会想到的是,先把链表遍历一遍,计算出链表的长度,然后计算出走到倒数第k个节点所需要走的步数,再用一个指针遍历此链表即可,但是这样做未免有点儿麻烦了,将链表遍历了2遍,大家仔细想想,有没有一次性就可以求出链表的倒数第K个节点,下面用了两个指针,一次性就将链表的倒数第K个节点求解出来了:
在这里插入图片描述如上图我们要求链表的倒数第二个节点,我们可以定义两个指针pA和pB,我们可以先让pB多走k-1步,然后再让pA一起和pB一起向后走,当pB走到最后一个节点的时候,pA就走到了倒数第K,以下是寻找倒数第k个节点的代码实现

ListNode* FindKthToTail(ListNode *pListNode, int k)//找到倒数第k个节点的值
{
	if(pListNode == NULL || k <= 0)
	{
		return NULL;
	}
	ListNode *pAhead = pListNode;
	ListNode *pBhead = NULL;
	int i = 1;
	while(i < k)
	{
		if(pAhead->next != NULL)
		{
			pAhead = pAhead->next;
			i++;
		}
		else
		{
			return NULL;
		}
	}

	pBhead = pListNode;
	while(pAhead->next != NULL)
	{
		pAhead = pAhead ->next;
		pBhead = pBhead->next;
	}
	return pBhead;
	}
	//上述代码还得考虑代码的鲁棒性,寻找的节点要在范围内

倒着打印链表(6题)

思路一:可以使用STL的stack依次将链表的数据压栈,然后再依次初栈打印,代码实现如下:

void PrintListRever(ListNode *phead)
{
	stack<int> mystack;

	ListNode *Node = phead;
	while (Node != nullptr)
	{
		mystack.push(Node->value);
		Node = Node->next;
	}

	while (!mystack.empty())
	{
		printf("%d ", mystack.top());
		mystack.pop();
	}
}

思路二:也可以使用递归进行逆序打印,代码实现如下:

void PrintListRever2(ListNode *phead)
{
	if (phead != nullptr)
	{
		if (phead->next != nullptr)
		{
			PrintListRever(phead->next);
		}
		printf("%d ", phead->value);
	}
}

在O(1)下删除链表节点(18题)

此题大家一看到都比较懵逼,怎么样才能在时间复杂度为O(1)下删除链表节点呢,首先函数的参数第一个是头指针,第二就是要删除节点的地址,思路如下:
首先我们将待删除节点的下一个节点的数据赋给删除节点,然后重置next域即可,当然了考虑到代码的鲁棒性,我们得判断待删节点是不是头结点,因为,如果是头结点,我们就得修改头指针,如果是尾节点,尾节点没有next节点,所以得单独考虑,代码实现如下:

void DeleteNode(ListNode **phead, ListNode* pToBeDelete)
{
	if (phead == nullptr || pToBeDelete == nullptr)
	{
		return;
	}
	if (pToBeDelete == *phead)
	{
		free(*phead);
		*phead = nullptr;
	}
	else if (pToBeDelete->next != nullptr)
	{
		pToBeDelete->value = pToBeDelete->next->value;
		ListNode *Node = pToBeDelete->next;
		pToBeDelete->next = pToBeDelete->next->next;
		free(Node);
	}
	else if (pToBeDelete->next == nullptr)
	{
		ListNode *Node = *phead;
		while (Node->next != pToBeDelete)
		{
			Node = Node->next;
		}
		free(Node->next);
		Node->next = nullptr;
	}
}

链表的逆置(24题)

思路:如图所示,我们讨论三个节点i,j,k
在这里插入图片描述假设之前的节点都已经调节好,我们对j进行操作,就得把j的next域指向i,所以,我们就得记录i的地址,当然也得记录j的next节点,不然,链表就断了,所以得需要三个指针来控制链表的逆置,代码如下

ListNode* ResverList(ListNode **phead)//通过三个指针来控制逆置
{
	ListNode *PreNode = nullptr;
	ListNode *pNode = *phead;
	ListNode *ReserveNode = nullptr;

	while (pNode != nullptr)
	{
		ListNode *pNext = pNode->next;
		if (pNext == nullptr)
		{
			ReserveNode = pNode;
		}
		
		pNode->next = PreNode;
		PreNode = pNode;
		pNode = pNext;
	}
	return ReserveNode;
}

寻找链表环的起点(23题)

思路:1、我们首先得知道如何判断链表是否有环:使用快慢指针,同时指向头,快指针每次走两步,慢指针每次走一步,如果快慢指针相遇说明,链表已成环,如果说快指针走到了末尾,任然未与慢指针相遇,则说明没有成环。下述代码如果不成环返回空,如果成环返回相遇点,代码实现如下:

ListNode* MeetingNode(ListNode *pHead)
{
	if (pHead == nullptr)
	{
		return nullptr;
	}
	ListNode *pSlow = pHead->next;
	if (pSlow == nullptr)
	{
		return nullptr;
	}

	ListNode *pFast = pSlow->next;

	while (pFast != nullptr && pSlow != nullptr)
	{
		if (pFast == pSlow)
		{
			return pFast;
		}

		pSlow = pSlow->next;
		pFast = pFast->next;
		if (pFast != nullptr)
		{
			pFast = pFast->next;
		}
	}
	return nullptr;
}

2、接下来我们来分析一下如何找到环的入口节点,如图分析:
在这里插入图片描述
代码实现如下:

ListNode *EntryNodeOfLoop(ListNode *pHead)
{
	ListNode *meetingNode = MeetingNode(pHead);
	if (meetingNode == nullptr)
	{
		return nullptr;
	}
	int noodsInLoop = 1;
	ListNode* pNode1 = meetingNode;
	while (pNode1->next != meetingNode)
	{
		pNode1 = pNode1->next;
		++noodsInLoop;
	}
	//上述代码计算了环的节点个数,下面用快慢指针寻找环的入口节点

	ListNode *pSlow = pHead;
	ListNode *pFast = pHead;
	for (int i = 0; i < noodsInLoop; i++)
	{
		pFast = pFast->next;
	}
	while (pFast != pSlow)
	{
		pFast = pFast->next;
		pSlow = pSlow->next;
	}

	return pFast;
}

寻找两个链表的第一个公共节点(52题)

思路如下:首先,得先遍历两个链表的长度,然后求出两个链表长度之差n,然后定义两个指针,指向链表的头,让长链表那个先走n步,然后再让短链表的指针和长链表的指针一起向前走,当两个指针所指向的地址相同,就找到了链表的第一个公共节点,代码实现如下:

在这里插入图片描述

int GetListLength(ListNode *pHead)
{
	ListNode *pNode = pHead;
	
	int length = 0;
	while (pNode != nullptr)
	{
		++length;
		pNode = pNode->next;
	}
	return length;
}
ListNode *FindDirstCommonNode(ListNode *phead1, ListNode *phead2)
{
	if (phead1 == nullptr || phead2 == nullptr)
	{
		return nullptr;
	}

	int lengthOfphead1 = GetListLength(phead1);
	int lengthOfphead2 = GetListLength(phead2);

	int nLengthDif = lengthOfphead1 - lengthOfphead2;

	ListNode* ListOfLong = phead1;
	ListNode* ListOfShort = phead2;

	if (lengthOfphead1 < lengthOfphead2)
	{
		ListOfLong = phead2;
		ListOfShort = phead1;
		nLengthDif = -nLengthDif;
	}

	for (int i = 0; i < nLengthDif; i++)
	{
		ListOfLong = ListOfLong->next;
	}

	while (ListOfLong != nullptr && ListOfShort != nullptr)
	{
		if (ListOfLong == ListOfShort)
		{
			return ListOfLong;
		}
		ListOfLong = ListOfLong->next;
		ListOfShort = ListOfShort->next;
	}
	return nullptr;
}

将两个有序链表连接成一个有序链表(25题)

思路:在数组中,我们可以将两个有序数组组合成一个有序数组(归并排序里),其实链表与数组的思路相似,先做比较后再进行合并,最后将链表后面的元素连接在链表的后面即可,代码如下:

ListNode* MergeList(ListNode *phead1, ListNode* phead2)
{
	if (phead1 == nullptr)
	{
		return phead2;
	}
	if (phead2 == nullptr)
	{
		return phead1;
	}
	
	ListNode* pNode = nullptr;//记录链表的头指针
	ListNode* p = nullptr;//停留在最后一个节点上,调整next域
	ListNode* pNode1 = phead1;//遍历phead1
	ListNode* pNode2 = phead2;//遍历phead2
	if (phead1->value < phead2->value)
	{
		p = pNode = phead1;
		pNode1 = pNode1->next;
	}
	else
	{
		p = pNode = phead2;
		pNode2 = pNode2->next;
	}

	while (pNode1 != nullptr && pNode2 != nullptr)
	{
		if (pNode1->value < pNode2->value)
		{
			p->next = pNode1;
			p = pNode1;
			pNode1 = pNode1->next;
		}
		else
		{
			p->next = pNode2;
			p = pNode2;
			pNode2 = pNode2->next;
		}
	}
	if (pNode1 != nullptr)//如果phead1还有元素,就将剩下元素连上即可
	{
		p->next = pNode1;
	}
	if (pNode2 != nullptr)//如果phead2还有元素,就将剩下的元素连上即可
	{
		p->next = pNode2;
	}
	return pNode;
}

以上就是关于剑指offer上有关链表的题目的解答思路及代码,如有错误,请指出

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值