【算法精练】轻松get单链表面试题
前言
- 删除无头单链表的非尾节点(不能遍历单链表)
- 在无头单链表非头节点前插入值为data的节点
- 用单链表实现约瑟夫环
- 逆置单链表(利用三个指针进行逆置)
- 逆置单链表(头插法)
- 对单链表进行冒泡排序(升序)
- 查找单链表的中间节点(只能遍历一次链表)
- 查找单链表的倒数第K个节点(只能遍历一次链表)
- 删除单链表倒数第K个节点
- 合并两个有序单链表,合并之后依然有序
- 求单链表中结点的个数
- 判断一个单链表中是否有环
- 判断两个单链表是否相交
- 求两个单链表相交的第一个节点
- 已知一个单链表中存在环,求进入环中的第一个节点
- 给出一单链表头指针pHead和一节点指针pToBeDeleted,O(1)时间复杂度删除节点pToBeDeleted
删除无头单链表的非尾节点(不能遍历单链表)
分为两种情况:①删除的位置pos为Null,直接返回 ②删除的位置不为空时,实现如下图所示:
void DeleteNotTailNode(PNode pos)
{
if(NULL == pos)
{
return;
}
PNode pDel = pos->next;
pos->data = pDel->data;
pos->next = pDel->next;
free(pDel);
}
在无头单链表非头节点前插入值为data的节点
分为两种情况:①插入的位置pos为Null,直接返回 ②插入的位置不为空时,实现如下图所示:
void InsertNotHeadNode(PNode pos,DataType _data)
{
if(NULL == pos)
return;
PNode NewNode = BuyNode(pos->data);
if(NewNode)
{
NewNode->next = pos->next;
pos->next = NewNode;
pos->data = _data;
}
}
用单链表实现约瑟夫环
分为两种情况:①链表为空时,直接返回Null ②链表不为空时,实现如下图所示:
PNode JosephCircle(PNode pHead,size_t M)
{
if(NULL =pHead)
return NULL;
PNode pCur = pHead;
while(pCur != pCur->next)
{
size_t k = M;
while(--k)
{
pCur = pCur->next;
}
PNode pDel = pCur->next;
pCur->data = pDel->data;
pCur->next = pDel->next;
free(pDel);
}
return pCur;
}
逆置单链表(利用三个指针进行逆置)
分为两种情况:①链表为空或者链表中仅有一个节点,直接返回pHead ②链表中有多个节点时,实现如下图所示:
PNode ReverseList(PNode pHead)
{
if(NULL == pHead || NULL == pHead->next)
return pHead;
PNode pPre = pHead;
PNode pCur = pPre->next;
PNode pSul = pCur->next;
while(pSul)
{
pCur->next = pPre;
pPre = pCur;
pCur = pSul;
if(pSur)
pSul= pSul->next;
}
pCur->next = pPre;
pHead->next = NULL;
return pCur;
}
逆置单链表(头插法)
分为两种情况:①链表为空或者链表中仅有一个节点,直接返回pHead ②链表中有多个节点时,实现如下图所示:
PNode ReverseList_P(PNode pHead)
{
if(NULL == pHead || NULL == pHead->next)
return pHead;
PNode pCur = pHead->next;
PNode NewNode = NULL;
while(pCur)
{
pHead->next = NewNode;
NewNode = pHead;
pHead = pCur;
pCur = pCur->next;
}
pHead->next = NewNode;
NewNode = pHead;
return NewNode;
}
对单链表进行冒泡排序(升序)
分为两种情况:①链表为空或者链表中仅有一个节点,直接返回 ②链表中有多个节点时,实现如下图所示:
void BubbleSort(PNode pHead)
{
if(NULL == pHead || NULL == pHead->next)
return;
int flag = 0;
PNode pCur = pHead;
PNode pTail = NULL;
while(pTail != pHead)
{
pCur = pHead;
while(pCur->next != pTail)
{
if(pCur->data > pCur->next->data)
{
DataType temp = pCur->data;
pCur->data = pCur->next->data;
pCur->next->data = temp;
flag = 1;
}
pCur = pCur->next;
if(!flag)
return;
}
pTail = pCur;
}
}
查找单链表的中间节点(只能遍历一次链表)
①偶数节点,如下图所示:
②奇数节点,如下图所示:
PNode FindMidNode(PNode pHead)
{
if(NULL == pHead || NULL == pHead->next)
return pHead;
PNode pSlow = pHead;
PNode pFast = pHead;
while(pFast && pFast->next)
{
pSlow = pSlow->next;
pFast = pFast->next->next;
}
return pSlow;
}
查找单链表的倒数第K个节点(只能遍历一次链表)
分为两种情况:①链表为空或K=0,直接返回Null ②链表有一个或多个结点,并且K>0时,如下图所示:
PNode FindLastKNode(PNode pHead, size_t K)
{
if(NULL ==pHead || K == 0)
return NULL;
PNode pSlow = pHead;
PNode pFast = pHead;
while(--K)
{
if(NULL == pFast)
return NULL;
pFast = pFast->next;
}
while(pFast->next)
{
pSlow = pSlow->next;
pFast = pFast->next;
}
return pSlow;
}
删除单链表倒数第K个节点
删除单链表可以参照上面的查找程序及图例,道理同上。
PNode DeleteLastKNode(PNode pHead, size_t K)
{
if(NULL == pHead || K == 0)
return NULL;
PNode pos = FindLastKNode(pHead,K);
Erase(&pHead,pos);
}
合并两个有序单链表,合并之后依然有序
分为三种情况:①pHead1为空时,返回pHead2 ②pHead2为空时,返回pHead1 ③当pHead1与pHead2都不为空时,实现如下图所示:
//法一:
PNode MergeList(PNode pHead1,PNode pHead2)
{
if(NULL == pHead1)
return pHead2;
if(NULL == pHead2)
return pHead1;
PNode NewNode = NULL;
PNode pTail = NULL;
if(pHead1->data < pHead2->data)
{
pTail =pHead1;
pHead1 = pHead1->next;
}
else
{
pTail = pHead2;
pHead2 = pHead2->next;
}
NewNode = pTail;
while(pHead1 && pHead2)
{
if(pHead1->data < pHead2->data)
{
pTail->next = pHead1;
pTail = pHead1;
pHead1 = pHead1->next;
}
else
{
pTail->next = pHead2;
pTail = pHead2;
pHead2 = pHead2->next;
}
}
if(pHead1)
pTail->next = pHead1;
else
pTail->next = pHead2;
return NewNode;
}
//法二:
PNode MergeList(PNode pHead1,PNode pHead2)
{
if(NULL == pHead1)
return pHead2;
if(NULL == pHead2)
return pHead1;
PNode NewNode = (PNode)malloc(sizeof(Node));
NewNode->data = -1;
PNode pTail = NewNode;
while(pHead1 && pHead2)
{
if(pHead1->data < pHead2->data)
{
pTail->next = pHead1;
pHead1 = pHead1->next;
}
else
{
pTail->next = pHead2;
pHead2 = pHead2->next;
}
pTail = pTail->next;
}
if(pHead1)
pTail->next = pHead1;
else
pTail->next = pHead2;
return NewNode->next;
}
//递归
PNode MergeList(PNode pHead1,PNode pHead2)
{
if(NULL == pHead1)
return pHead2;
if(NULL == pHead2)
return pHead1;
PNode NewNode = (PNode)malloc(sizeof(Node));
NewNode->data = -1;
PNode pTail = NewNode;
if(pHead1->data < pHead2->data)
{
pTail = pHead1;
pTail->next = MergeList(pHead1->next,pHead2);
}
else
{
pTail = pHead2;
pTail->next = MergeList(pHead1,pHead2->next);
}
return NewNode->next;
}
求单链表中结点的个数
这是最最基本的了,应该能够迅速写出正确的代码,注意检查链表是否为空。时间复杂度为O(n)。参考代码如下:
// 求单链表中结点的个数
unsigned int GetListLength(ListNode * pHead)
{
if(pHead == NULL)
return 0;
unsigned int nLength = 0;
ListNode * pCurrent = pHead;
while(pCurrent != NULL)
{
nLength++;
pCurrent = pCurrent->m_pNext;
}
return nLength;
}
判断一个单链表中是否有环
这里也是用到两个指针。如果一个链表中有环,也就是说用一个指针去遍历,是永远走不到头的。因此,我们可以用两个指针去遍历,一个指针一次走两步,一个指针一次走一步,如果有环,两个指针肯定会在环中相遇。时间复杂度为O(n)。参考代码如下:
bool HasCircle(ListNode * pHead)
{
ListNode * pFast = pHead; // 快指针每次前进两步
ListNode * pSlow = pHead; // 慢指针每次前进一步
while(pFast != NULL && pFast->m_pNext != NULL)
{
pFast = pFast->m_pNext->m_pNext;
pSlow = pSlow->m_pNext;
if(pSlow == pFast) // 相遇,存在环
return true;
}
return false;
}
在这里插入代码片
判断两个单链表是否相交
如果两个链表相交于某一节点,那么在这个相交节点之后的所有节点都是两个链表所共有的。也就是说,如果两个链表相交,那么最后一个节点肯定是共有的。先遍历第一个链表,记住最后一个节点,然后遍历第二个链表,到最后一个节点时和第一个链表的最后一个节点做比较,如果相同,则相交,否则不相交。时间复杂度为O(len1+len2),因为只需要一个额外指针保存最后一个节点地址,空间复杂度为O(1)。参考代码如下:
bool IsIntersected(ListNode * pHead1, ListNode * pHead2)
{
if(pHead1 == NULL || pHead2 == NULL)
return false;
ListNode * pTail1 = pHead1;
while(pTail1->m_pNext != NULL)
pTail1 = pTail1->m_pNext;
ListNode * pTail2 = pHead2;
while(pTail2->m_pNext != NULL)
pTail2 = pTail2->m_pNext;
return pTail1 == pTail2;
}
求两个单链表相交的第一个节点
对第一个链表遍历,计算长度len1,同时保存最后一个节点的地址。
对第二个链表遍历,计算长度len2,同时检查最后一个节点是否和第一个链表的最后一个节点相同,若不相同,不相交,结束。
两个链表均从头节点开始,假设len1大于len2,那么将第一个链表先遍历len1-len2个节点,此时两个链表当前节点到第一个相交节点的距离就相等了,然后一起向后遍历,知道两个节点的地址相同。
时间复杂度,O(len1+len2)。参考代码如下:
ListNode* GetFirstCommonNode(ListNode * pHead1, ListNode * pHead2)
{
if(pHead1 == NULL || pHead2 == NULL)
return NULL;
int len1 = 1;
ListNode * pTail1 = pHead1;
while(pTail1->m_pNext != NULL)
{
pTail1 = pTail1->m_pNext;
len1++;
}
int len2 = 1;
ListNode * pTail2 = pHead2;
while(pTail2->m_pNext != NULL)
{
pTail2 = pTail2->m_pNext;
len2++;
}
if(pTail1 != pTail2) // 不相交直接返回NULL
return NULL;
ListNode * pNode1 = pHead1;
ListNode * pNode2 = pHead2;
// 先对齐两个链表的当前结点,使之到尾节点的距离相等
if(len1 > len2)
{
int k = len1 - len2;
while(k--)
pNode1 = pNode1->m_pNext;
}
else
{
int k = len2 - len1;
while(k--)
pNode2 = pNode2->m_pNext;
}
while(pNode1 != pNode2)
{
pNode1 = pNode1->m_pNext;
pNode2 = pNode2->m_pNext;
}
return pNode1;
}
已知一个单链表中存在环,求进入环中的第一个节点
首先判断是否存在环,若不存在结束。在环中的一个节点处断开(当然函数结束时不能破坏原链表),这样就形成了两个相交的单链表,求进入环中的第一个节点也就转换成了求两个单链表相交的第一个节点。参考代码如下:
ListNode* GetFirstNodeInCircle(ListNode * pHead)
{
if(pHead == NULL || pHead->m_pNext == NULL)
return NULL;
ListNode * pFast = pHead;
ListNode * pSlow = pHead;
while(pFast != NULL && pFast->m_pNext != NULL)
{
pSlow = pSlow->m_pNext;
pFast = pFast->m_pNext->m_pNext;
if(pSlow == pFast)
break;
}
if(pFast == NULL || pFast->m_pNext == NULL)
return NULL;
// 将环中的此节点作为假设的尾节点,将它变成两个单链表相交问题
ListNode * pAssumedTail = pSlow;
ListNode * pHead1 = pHead;
ListNode * pHead2 = pAssumedTail->m_pNext;
ListNode * pNode1, * pNode2;
int len1 = 1;
ListNode * pNode1 = pHead1;
while(pNode1 != pAssumedTail)
{
pNode1 = pNode1->m_pNext;
len1++;
}
int len2 = 1;
ListNode * pNode2 = pHead2;
while(pNode2 != pAssumedTail)
{
pNode2 = pNode2->m_pNext;
len2++;
}
pNode1 = pHead1;
pNode2 = pHead2;
//法一:用公式法计算
//法二:先对齐两个链表的当前结点,使之到尾节点的距离相等
if(len1 > len2)
{
int k = len1 - len2;
while(k--)
pNode1 = pNode1->m_pNext;
}
else
{
int k = len2 - len1;
while(k--)
pNode2 = pNode2->m_pNext;
}
while(pNode1 != pNode2)
{
pNode1 = pNode1->m_pNext;
pNode2 = pNode2->m_pNext;
}
return pNode1;
}
给出一单链表头指针pHead和一节点指针pToBeDeleted,O(1)时间复杂度删除节点pToBeDeleted
对于删除节点,我们普通的思路就是让该节点的前一个节点指向该节点的下一个节点,这种情况需要遍历找到该节点的前一个节点,时间复杂度为O(n)。对于链表,链表中的每个节点结构都是一样的,所以我们可以把该节点的下一个节点的数据复制到该节点,然后删除下一个节点即可。要注意最后一个节点的情况,这个时候只能用常见的方法来操作,先找到前一个节点,但总体的平均时间复杂度还是O(1)。参考代码如下:
void Delete(ListNode * pHead, ListNode * pToBeDeleted)
{
if(pToBeDeleted == NULL)
return;
if(pToBeDeleted->m_pNext != NULL)
{
pToBeDeleted->m_nKey = pToBeDeleted->m_pNext->m_nKey; // 将下一个节点的数据复制到本节点,然后删除下一个节点
ListNode * temp = pToBeDeleted->m_pNext;
pToBeDeleted->m_pNext = pToBeDeleted->m_pNext->m_pNext;
delete temp;
}
else // 要删除的是最后一个节点
{
if(pHead == pToBeDeleted) // 链表中只有一个节点的情况
{
pHead = NULL;
delete pToBeDeleted;
}
else
{
ListNode * pNode = pHead;
while(pNode->m_pNext != pToBeDeleted) // 找到倒数第二个节点
pNode = pNode->m_pNext;
pNode->m_pNext = NULL;
delete pToBeDeleted;
}
}
}