文章目录
链表结构的定义
对于链表的操作,在面试中经常会被提及,因为面试时间内有限,而链表代码的描述以及操作难度适中,所以在面试中被提及到的概率比较高,我将剑指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上有关链表的题目的解答思路及代码,如有错误,请指出