单链表经典面试题详解

面试官通过考察应聘者的编程语言、数据结构和算法来判断应聘者是否具备扎实的基础知识。而单链表又是数据结构中最基本的知识。

在之前的文章中,我们已经介绍过了单链表的基本功能的代码实现,下面我们就基于基本的功能,来实现一些关于单链表的经典面试题的代码编写。

链表结构定义:
typedef int DataType;

typedef struct LinkNode
{
    DataType _data;//当前节点保存数据
    struct LinkNode* _pNext;//指向链表中下一结点的指针

}Node;
逆序打印单链表:

使用递归的方法来打印单链表。

//逆序打印单链表
void PrintFromTail2Front(Node* pHead)
{
    if (pHead == NULL)
        return;
    PrintFromTail2Front(pHead->_pNext);
    printf("%d-->", pHead->_data);
    printf("\n");
}
删除一个无头的单链表非尾结点(不能遍历链表):

实现起来也很简单,主要思想是删除pos位置的下一个节点,因为单链表只可以找到当前结点的下一个节点。
这里写图片描述

//删除一个无头的单链表非尾结点(不能遍历链表)
void DelnotTailNode(Node* pos)
{
    //确认pos位置及其下一位的合法性
    assert(pos);
    assert(pos->_pNext);

    Node* pD = pos->_pNext;
    pos->_data = pD->_data;
    pos->_pNext = pD->_pNext;
    free(pD);
    pD = NULL;

}
不允许遍历链表,在pos之后插入

这里写图片描述

//不允许遍历链表,在pos之后插入
void LinkListInsertAfter( Node* pos, DataType data)
{
    assert(pos);
    if (pos == NULL)
    {
        return;
    }
    Node* NewNode = LinkListCreatNode(data);
    NewNode->_pNext = pos->_pNext;
    pos->_pNext = NewNode;

}
不允许遍历链表,在pos之前插入

这个问题我是在基于在pos之后插入的问题解决,只要在pos之后插入,然后交换pos和pos->next的值,就相当于在不遍历链表的前提下完成了在pos之前插入。
这里写图片描述

//不允许遍历链表,在pos之前插入
void LinkListInsertBefore(Node** pHead, Node* pos, DataType data)
{
    assert(pHead);
    assert(*pHead);
    LinkListInsertAfter(pos, data);
    DataType tmp = pos->_data;
    pos->_data = pos->_pNext->_data;
    pos->_pNext->_data = tmp;

}
单链表实现约瑟夫环

这里写图片描述

//单链表实现约瑟夫环(JosephCircle)
Node* JosephCircle(Node* pHead, int  M)
{
    assert(pHead);
    Node* pCur = pHead;
    Node* pDel = NULL;
    //在删除一个结点之后,要让其前一个节点指向其后一个结点,
    //若其前一个节点指向其后一个结点使同一个结点
    //则说明环中只剩最后一个结点结束
    while (pCur->_pNext!=pCur)
    {
        //1.报数
        int count = M;
        while (--count)
            pCur = pCur->_pNext;
        //2.删除结点
        //删除pCur的下一个结点,将pCur与pCur->_pNext的data互换
        pDel = pCur->_pNext;
        pCur->_data = pDel->_data;
        pCur->_pNext = pDel->_pNext;
        free(pDel);

    }
    return pCur;
}
查找单链表中间结点,要求只能遍历一次链表

这里写图片描述

//查找单链表的中间节点,要求只能遍历一次链表
Node* LinkFindMiddeNode(Node* pHead)
{
    assert(pHead);
    Node* pFast = pHead;
    Node* pSlow = pHead;

    while (pFast && pFast->_pNext!=NULL)
    {
        pFast = pFast->_pNext->_pNext;
        pSlow = pSlow->_pNext;
    }
    printf("中间结点是:%d", pSlow->_data);
    return pSlow;
}
查找单链表的倒数第K个结点,要求只能遍历一次链表

这里写图片描述

//查找单链表的倒数第K个结点,要求只能遍历一次链表
Node* LinkFindLastKNode(Node* pHead,int K)
{
    assert(pHead);
    Node* pFast = pHead;
    Node* pSlow = pHead;
    for (int i = 0; i < K; i++)
    {
        if (pFast == NULL)
            return 0;
        //先让快指针走K-1步
        pFast = pFast->_pNext;
    }
    while (pFast&& pFast->_pNext != NULL)
    {
        pFast = pFast->_pNext;
        pSlow = pSlow->_pNext;
    }
    printf("倒数第%d个结点是%d\n", K, pSlow->_data);
    return pSlow;
}
删除单链表倒数第K个结点

这里写图片描述

//删除链表的倒数第K个结点
void LinkDelLastKNode(Node* pHead, int K)
{
    assert(pHead);
    size_t len = LinkListSize(&pHead);
    if (K > len)
        return;
    //当删除结点为倒数第size个时,相当头删
    if (K == len)
    {
        LinkListPopFront(pHead);//头删包含头指针的指向改变
        return;
    }
    //找到倒数第K个结点
    Node* pFast = pHead;
    for (int i = 0; i < len-K-1; i++)
    {
        pFast = pFast->_pNext;
    }
    //删除该结点
    Node* pDel = pFast->_pNext;
    pFast->_pNext = pDel->_pNext;
    LinkListIDestroyNode(pDel);
}
单链表的逆置(1)—改变指针指向

这里写图片描述

//链表的逆置----改变
//方式一:改变指针指向
void* LinkListReverse1(Node** pHead)
{
    assert(pHead);
    Node* pPre = NULL;
    Node* pCur = *pHead;
    Node* pNext = NULL;
    while (pCur)
    {
        pNext = pCur->_pNext;
        pCur->_pNext = pPre;
        pPre = pCur;
        pCur = pNext;
    }
    *pHead = pPre;
    return;
}
单链表的逆置(2)—采取头插的思想

这里写图片描述

//方式二:头插方式
Node* LinkListReverse2(Node* pHead)
{

    Node* NewpHead = NULL;
    Node* pCur = pHead;
    Node* pNext = NULL;

    while (pCur)
    {
        pNext = pCur->_pNext;
        pCur->_pNext = NewpHead;
        NewpHead = pCur;
        pCur = pNext;
    }
    return NewpHead;
}
合并两个有序链表,合并之后依然有序

这里写图片描述

//合并两个有序链表,合并之后依然有序
Node* LinkListMerge(Node* pHead1, Node* pHead2)
{
    assert(pHead1);
    assert(pHead2);
    Node* pCur1 = pHead1;
    Node* pCur2 = pHead2;
    Node* new_head = NULL;
    Node* new_tail = NULL;
    //尾插第一个结点
    if (pCur1->_data >= pCur2->_data)
    {
        new_head = new_tail= pCur2;
        pCur2 = pCur2->_pNext;
    }
    else
    {
        new_head = new_tail = pCur1;
        pCur1 = pCur1->_pNext;
    }
    //尾插后续结点
    while (pCur1  && pCur2 )
    {
        if (pCur1->_data >= pCur2->_data)
        {
            new_tail->_pNext = pCur2;
            pCur2 = pCur2->_pNext;
        }
        else
        {
            new_tail->_pNext = pCur1;
            pCur1 = pCur1->_pNext;
        }
        new_tail = new_tail->_pNext;
    }
    //当pHead1还有剩余结点时,将剩余结点尾插至NewpHead
    if (pCur1)
        new_tail->_pNext = pCur1;
    //当pHead2还有剩余结点时,将剩余结点尾插至NewpHead
    else  //(pCur2)
        new_tail->_pNext = pCur2;

    return new_head;
}
单链表的冒泡排序

冒泡排序的基本思想是将相邻两元素间两两比较,一趟排序过后,最大的元素就到达了最后。单链表的冒泡排序也是如此,万变不离其宗,我们直接上代码:
这里写图片描述

//单链表的冒泡排序
//冒泡排序的基本思想是将相邻两元素间两两比较,一趟排序过后,
//最大的元素就到达了最后。单链表的冒泡排序也是如此,万变不离其宗,我们直接上代码:

//升序
void LinkBubbleSort(Node* pHead)
{
    //空链表或者链表只有一个结点,都不用排序
    if (pHead == NULL || pHead->_pNext == NULL)
    {
        return;
    }
    Node* pTail = NULL;
    while (pHead != pTail)
    {
        Node* pPre = pHead;
        Node* pCur = pPre->_pNext;
        while (pCur != pTail)
        {
            if (pPre->_data > pCur->_data)
            {
                DataType temp = pPre->_data;
                pPre->_data = pCur->_data;
                pCur->_data = temp;
            }
            pPre = pCur;
            pCur = pCur->_pNext;
        }
        pTail = pPre;
    }
}
判断链表是否带环

这里写图片描述

//判断链表是否带环
void If_HasCycle(Node* pHead)
{
    if (pHead == NULL || pHead->_pNext == NULL)
    {
        return;
    }
    Node* pFast = pHead;
    Node* pSlow = pHead;
    while (pFast != NULL)
    {
        pFast = pFast->_pNext->_pNext;
        pSlow = pSlow->_pNext;
        if (pFast == pSlow)
            return pSlow;
    }           
    return NULL;
}
若链表带环,求环长度

判断是否带环函数返回值即为相遇点(两指针在环内的交点),然后把环遍历一遍就行了
这里写图片描述

//若带环,求环长度
size_t LinkCycle_Len(Node* pHead)
{
    assert(pHead);

    //判断是否带环函数返回值即为相遇点
    Node* Meet_Node = If_HasCycle(pHead);
    if (Meet_Node == NULL)
    {
        return 0;
    }
    size_t len = 1;
    Node* pCur = Meet_Node->_pNext;

    for (pCur; pCur != Meet_Node; pCur = pCur->_pNext)
    {
        len++;
    }
    return len;
}
若链表带环,求环的入口点

这个问题需要一定的数学推理,先把结论列出来:
相遇点node到入口点的距离=头指针到连接点的距离,因此,分别从相遇点、头指针开始走,相遇的那个点就是连接
假设链表总长为L,头节点到入口点的距离为a,入口点到快慢指针交点距离为x,环的长度为R,现在假设慢指针走了S步与快指针相遇,
s = a + x;
那么快指针走了2S步,
2s = a + nr + x;
就可以得到:a + x = nr;
->a = nr - x;
可以看出来,头节点到入口点的距离等于交点到入口点的距离
那我们让两个指针,一个从相遇点走,一个从头节点走,最后一定在入口点相遇。

先判断链表是否带环,若带环,则返回的即是相遇点,定义两个指针一个指向pHead,一个指向相遇点,两指针均一次走一步,则两指针相遇处即为环的入口点
这里写图片描述

//若带环,求环的入口点
Node* GetCycleEntry(Node* pHead)
{
    if (pHead == NULL ||pHead->_pNext==NULL)
    {
        return NULL;
    }
    Node* Meet_Node = If_HasCycle(pHead);
    if (Meet_Node == NULL)
    {
        return NULL;
    }
    Node* pCur = pHead;
    Node* pM = Meet_Node;
    while (pCur != pM)
    {
        pCur = pCur->_pNext;
        pM = pM->_pNext;
    }
    return pCur;
}
求两个已排序单链表中相同的数据

这里写图片描述

//求两个已排序单链表中相同的数据
Node* UnionSet(Node* pHead1, Node* pHead2)
{
    if (pHead1 == NULL || pHead2 == NULL)
    {
        return;
    }
    Node* pCur1 = pHead1;
    Node* pCur2 = pHead2;
    Node* newHead = NULL;
    Node* newTail = NULL;
    while (pCur1 && pCur2)
    {
        if (pCur1->_data < pCur2->_data)
            pCur1 = pCur1->_pNext;
        else if (pCur1->_data>pCur2->_data)
            pCur2 = pCur2->_pNext;
        else
        {
            Node* new_Node = LinkListCreatNode(pCur1->_data);
            if (newHead == NULL)
                newHead = newTail = new_Node;
            else
            {
                newTail->_pNext = new_Node;
                newTail = newTail->_pNext;
            }   
            pCur1 = pCur1->_pNext;
            pCur2 = pCur2->_pNext;
        }
    }
    return newHead;
}
复杂链表的复制

一个链表的每个节点,有一个next指针指向下一个节点,还有一个random指针指向这个链表中的一个随机节点或者NULL,现在要求实现复制这个链表,返回复制后的新链表
准备工作:
1.定义复杂链表结构类型

//定义复杂链表的结构类型
typedef struct ComplexNode
{
    DataType _data;//当前结点数据
    struct ComplexNode* _pNext;//指向下一结点的指针
    struct ComplexNode* _random;//指向随机结点或者NULL的指针
}CNode;

2.编写三个基本功能的函数来实现初始化、打印、创造节点和插入结点

//初始化
void ComplexPrint(CNode* pHead)
{
    assert(pHead);
    CNode* pCur = pHead;
    for (pCur; pCur != NULL; pCur = pCur->_pNext)
    {
        printf("%d--->", pCur->_data);
    }
    printf("NULL\n");
}
//打印
void ComplexInit(CNode** pHead)
{
    assert(pHead);
    *pHead = NULL;
}
//创建新结点
CNode* ComplexCreatNode(DataType data)
{
    CNode* newNode = (CNode*)malloc(sizeof(CNode));

    newNode->_data = data;
    newNode->_pNext = NULL;
    newNode->_random = NULL;
    return newNode;
}
//尾插
void ComplexLinkPushBack(CNode** pHead, DataType data)
{
    CNode* NewNode = ComplexCreatNode(data);
    assert(pHead);
    //如果链表为空,则直接让头指针指向新申请的节点即可
    if (*pHead == NULL)
    {
        *pHead = NewNode;
        return;
    }
    //否则从头开始遍历链表,直到当前节点的指针域指向NULL,然后让当前节
    //点的指针域指向新申请的节点即可
    CNode* pTail = *pHead;
    while (pTail->_pNext)
    {
        pTail = pTail->_pNext;
    }
    pTail->_pNext = NewNode;
}

3.复杂链表的复制

//在原链表中求各random的相对偏移量
size_t Diff(CNode* src,CNode* dst)
{
    if (src == NULL || dst == NULL)
    {
        return (size_t)-1;
    }
    size_t count = 0;
    while(src != NULL)
    {
        if (src == dst)
            break;
        ++count;
        src = src->_pNext;
    }
    return count;
}
//求偏移位数
CNode* Step(CNode* pos,size_t offset)
{
    size_t i = 0;
    for (; i < offset; i++)
    {
        if (pos == 0)
            return NULL;
        pos = pos->_pNext;
    }
    return pos;
} 

最后实现复制目的的函数:

//复杂链表的复制
CNode* ComplexCopy(CNode* pHead)
{
    assert(pHead);
    //先将单链表进行简单复制
    CNode* pCur = pHead;
    CNode* newHead = NULL;
    CNode* newTail = NULL;
    for (pCur; pCur != NULL; pCur = pCur->_pNext)
    {
        CNode* newNode = ComplexCreatNode(pCur->_data);
        if (newHead == NULL)
            newHead = newTail = newNode;
        else
        {
            newTail->_pNext = newNode;
            newTail = newTail->_pNext;
        }
    }
    //再依次求每个random指针相对头节点的偏移量
    //根据偏移量,修改每个新链表节点的random指针
    CNode* new_pCur = newHead;
    for (pCur = pHead; pCur != NULL&&new_pCur != NULL;
        pCur = pCur->_pNext, new_pCur = new_pCur->_pNext)
    {
        //原始偏移量为空,不变
        if (pCur->_random == NULL)
        {
            new_pCur->_random = NULL;
            continue;
        }
        //根据偏移量,修改每个新链表节点的random指针  
        size_t offset = Diff(pHead, pCur->_random);
        new_pCur->_random = Step(newHead, offset);
    }
    return newHead;
}

猜你喜欢

转载自blog.csdn.net/tanrui519521/article/details/81011801