链表面试题

链表面试题

用C语言实现一些经典的关于链表的面试题
题目中如果直接出现某个函数调用可能是之前关于链表博客里面写的函数,具体可以点击此处

附上链表实现代码和面试题代码

链表实现代码
链表面试题代码

包含问题一览

void PrintHelp(SListNode* pHead);//1.从尾打印链表
void PrintFromTail(SListNode* pHead);
void DeleteNodeNotTail(SListNode* pHead);//2.删除一个非尾节点(不能遍历)
void SListInsert1(SListNode* pos, DataType data);//3.无头链表一个节点前插入链表(不能遍历)
SListNode* JosephCircle(SListNode* s, size_t circle_num);//4.单链实现约瑟夫环
void SListBubbleSort(SListNode* pHead);//5.逆置链表
void SListBubbleSort(SListNode* pHead);//6.冒泡排序
SListNode* SListUnion(SListNode* pHead1, SListNode* pHead2);//7.合并两个有序链表,并且链表仍有序
SListNode* FindMiddleNode(SListNode* pHead);//8.查找链表的中间结点
SListNode* FindLastKNode(SListNode* pHead, size_t k);//9.查找倒数第K个结点
void* DeleteLastKNode(SListNode** pHead, size_t k);//10.删除倒数第K个结点
//11.链表带环问题
SListNode* JudgeCycle(SListNode* pHead);//判断是否带环
size_t GetCycleLenth(SListNode* meet);//求环长度
SListNode* GetCycleEntrance(SListNode* pHead, SListNode* meet);//求环入口
SListNode* JudgeCross(SListNode* pHead1, SListNode* pHead2);//12.判断链表是否相交(不带环)
SListNode* JudgeCrossCycle(SListNode* pHead1, SListNode* pHead2);//13.判断链表是否相交(可以带环)
SCListNode* CopyComplexList(SCListNode* pHead);//复制复杂链表

解题思路及实现代码

1.从尾打印链表

思路比较简单,递归打印即可实现

void PrintFromTail(SListNode* pHead)
{
    PrintHelp(pHead);
    printf("\n");
}

void PrintHelp(SListNode* pHead)//从尾打印链表,辅助函数
{
    assert(pHead);
    if (pHead->_pNext != NULL)
    {
        PrintHelp(pHead->_pNext);
    }
    printf("%d ", pHead->_data);//递归打印
}

2.删除单链表的一个非尾节点(不能遍历)

删除节点需要找到要删除的节点前一个节点,但是由于不能遍历,无法得到该节点前一个节点,需要将问题进行转化,可将后一个节点数据前移,问题转化为删除目标节点的后一个节点,就能顺利解决,也是为什么题目中有“非尾节点”的要求,不知道要删除的节点的前一个节点是不可能删除的

欢迎访问jo_qzy的博客

void DeleteNodeNotTail(SListNode* pos)
{
    assert(pos);
    SListNode* cur = pos;
    SListNode* next = pos->_pNext;
    if (cur->_pNext == NULL)
    {
        printf("The node is tail.\n");
        return;
    }
    cur->_data = next->_data;//把下一节点的数据前移,问题转为删除后一个节点
    if (next->_pNext != NULL)
    {
        cur->_pNext = next->_pNext;
    }
    else
    {
        cur->_pNext = NULL;
    }
    free(next);
}

3.无头链表一个节点前插入链表(不能遍历)

同第二题,思路类似,插入依旧需要知晓前一个节点,所以可以将新节点插在目标节点后面,再对数据进行调整

void SListInsert1(SListNode* pos, DataType data)
{
    SListNode* cur = pos;
    SListNode* next = BuySListNode(cur->_data);
    next->_pNext = cur->_pNext;
    next->_data = cur->_data;
    cur->_pNext = next;
    cur->_data = data;
}

4.单链实现约瑟夫环

约瑟夫环并不难,实现只需要知道其原理即可,有疑问可点击上面链接了解约瑟夫环

SListNode* JosephCircle(SListNode* pHead, size_t circle_num)
{
    assert(pHead);
    SListNode* cur = pHead;
    SListNode* next = NULL;
    while (cur->_pNext)
    {
        cur = cur->_pNext;
    }
    cur->_pNext = pHead;
    cur = cur->_pNext;
    while (cur->_pNext != cur)
    {
        size_t count = circle_num;
        while (--count)
        {
            cur = cur->_pNext;
        }
        next = cur->_pNext;
        cur->_data = next->_data;
        cur->_pNext = next->_pNext;
        free(next);
    }
    return cur;
}

5.逆置链表

链表逆置可通过三个指针来实现,关键是理清三个指针的关系,实现至少需要三个指针

void ReverseSList(SListNode** ppHead)
{
    assert(*ppHead);
    SListNode* cur = (*ppHead);
    SListNode* new_head = NULL;
    SListNode* next;
    while (cur)
    {
        next = cur->_pNext;
        cur->_pNext = new_head;
        new_head = cur;
        cur = next;
    }
    (*ppHead) = new_head;
}

6.链表的冒泡排序

冒泡的关键在于控制冒泡结束的位置,设置指针记下结尾作为冒泡终止条件,即可完成冒泡排序的操作,我未进行优化,可以设置flag当数据未进行交换时,提前结束冒泡

void SListBubbleSort(SListNode* pHead)
{
    assert(pHead);
    SListNode* cur = pHead;
    SListNode* end = NULL;
    while (end != pHead)
    {
        while (cur->_pNext != end)
        {
            if (cur->_data > cur->_pNext->_data)
            {
                cur->_data ^= cur->_pNext->_data;
                cur->_pNext->_data ^= cur->_data;
                cur->_data ^= cur->_pNext->_data;
            }
            cur = cur->_pNext;
        }
        end = cur;
        cur = pHead;
    }
}

7.双链表合并,合并后依旧有序(归并排序思想)

有点类似于归并排序,谁小先拿谁当作数据插入后方

SListNode* SListUnion(SListNode* pHead1, SListNode* pHead2)
{
    SListNode* new_head = NULL, *cur = NULL;
    if (pHead1 == NULL && pHead2 == NULL)
    {
        return NULL;
    }
    if (pHead1 == NULL)
    {
        return pHead2;
    }
    if(pHead2 == NULL)
    {
        return pHead1;
    }
    if (pHead1->_data <= pHead2->_data)
    {
        new_head = pHead1;
        pHead1 = pHead1->_pNext;
    }
    else
    {
        new_head = pHead2;
        pHead2 = pHead2->_pNext;
    }
    cur = new_head;
    while (pHead1 && pHead2)
    {
        if (pHead1->_data <= pHead2->_data)
        {
            cur->_pNext = pHead1;
            pHead1 = pHead1->_pNext;
        }
        else
        {
            cur->_pNext = pHead2;
            pHead2 = pHead2->_pNext;
        }
        cur = cur->_pNext;
    }
    if (pHead1 == NULL)
    {
        cur->_pNext = pHead2;
    }
    else
    {
        cur->_pNext = pHead1;
    }
    return new_head;
}

8.查找链表的中间结点(思路:快慢指针法)

设置快慢指针,慢指针走一步,快指针走两步,循环只需要控制快指针的终止条件即可,当快指针结束指向末尾,慢指针即中点
注:若为偶数个节点,查找上中位数的节点

SListNode* FindMiddleNode(SListNode* pHead)
{
    assert(pHead);
    SListNode* fast = pHead;
    SListNode* cur = pHead;
    while (fast && fast->_pNext != NULL)
    {
        fast = fast->_pNext;
        if (fast->_pNext != NULL)
        {
            fast = fast->_pNext;
            cur = cur->_pNext;
        }
    }
    return cur;
}

9.查找链表倒数第K个结点

依旧是通过快慢指针实现,快指针先走k步,慢指针再和快指针同步走即可

SListNode* FindLastKNode(SListNode* pHead, size_t k)
{
    assert(pHead);
    SListNode* cur = pHead;
    SListNode* end = pHead;
    while (k--)
    {
        if (end == NULL)
        {
            return NULL;
        }
        end = end->_pNext;
    }
    while (end)
    {
        end = end->_pNext;
        cur = cur->_pNext;
    }
    return cur;
}

10.删除倒数第K个节点

这里通过几个链接使用的函数来实现功能,只要理解原理也可以写出(我说这些没别的,我懒)

void DeleteLastKNode(SListNode** ppHead, size_t k)
{
    SListErase(ppHead, FindLastKNode((*ppHead), k));
}

11.判断链表是否带环,带环求环长度和入口点

三个函数分别实现一个功能,是否带环可以通过快慢指针来判断,绝不能通过直接遍历(如果直接遍历,将出现死循环)

判断环:快指针走两步,慢指针走一步,当存在环,就变成追及问题,每次逼近一个节点,迟早能追上,所以不能快指针一次走三步,他只能一次走两步

求环长度:通过判断环函数返回相遇点,相遇点必定在环内,此时记下相遇点位置,循环一次即可

求入口点:相遇点指针每次走一步,另一指针从头开始每次走一步,当两者相遇,该点就是入口点
原因:相遇点开始走过的长度 + 头指针走过的长度相加 = 环长度的n倍

SListNode* JudgeCycle(SListNode* pHead)//判断是否带环
{
    assert(pHead);
    SListNode* fast = pHead;
    SListNode* slow = pHead;
    while (fast)
    {
        slow = slow->_pNext;
        fast = fast->_pNext;
        if (fast != NULL)
        {
            fast = fast->_pNext;
        }
        if (fast == slow)
        {
            return slow;
        }
    }
    return NULL;
}

size_t GetCycleLenth(SListNode* meet)//求环长度
{
    SListNode* cur = meet;
    size_t count = 1;
    while (cur->_pNext != meet)
    {
        cur = cur->_pNext;
        count++;
    }
    return count;
}

SListNode* GetCycleEntrance(SListNode* pHead, SListNode* meet)//求环的入口点
{
    while (pHead != meet)
    {
        pHead = pHead->_pNext;
        meet = meet->_pNext;
    }
    return meet;
}

12.判断链表是否相交(假设不带环)

思路非常简单,两个指针从头开始,直接走到尾,判断尾指针是否相同,即可判断
另一种思路:转换为环求入口点

SListNode* JudgeCross(SListNode* pHead1, SListNode* pHead2)
{
    //两种思路:
    //1.找到两个尾指针,若相同,相交,再长的链表先走相差的步数,两者同时走找交点
    //2.同样的找尾指针,若相同,说明相交,将尾连任意表的头构成环,转换成求环入口问题
    //这里只实现第一种
    assert(pHead1 && pHead2);
    SListNode* cur1 = pHead1;
    SListNode* cur2 = pHead2;
    SListNode* plong = NULL;
    SListNode* pshort = NULL;
    int count = 0;
    int count1 = 1, count2 = 1;
    while (cur1->_pNext != NULL)//均走到尾,若尾指针相同,说明相交
    {
        cur1 = cur1->_pNext;
        count1++;
    }
    while (cur2->_pNext != NULL)
    {
        cur2 = cur2->_pNext;
        count2++;
    }
    if (cur1 != cur2)
    {
        return NULL;
    }
    count = abs(count1 - count2);
    if (count1 > count2)
    {
        plong = pHead1;
        pshort = pHead2;
    }
    else
    {
        plong = pHead2;
        pshort = pHead1;
    }
    while (count--)
    {
        plong = plong->_pNext;
    }
    while (plong != pshort)
    {
        plong = plong->_pNext;
        pshort = pshort->_pNext;
    }
    return plong;
}

13.判断链表是否相交(设链表可以带环)

画图分析情况,对每种情况分别进行处理即可,大量调用了之前写的的函数

欢迎访问jo_qzy的博客

SListNode* JudgeCrossCycle(SListNode* pHead1, SListNode* pHead2)
{
    SListNode* cur1 = JudgeCycle(pHead1);
    SListNode* cur2 = JudgeCycle(pHead2);
    SListNode* traversal = NULL;
    if (cur1 == NULL && cur2 == NULL)//情况1:都不带环
    {
        return JudgeCross(pHead1, pHead2);
    }
    if (cur1 == NULL || cur2 == NULL)//情况2:一个有环一个无环,不可能相交
    {
        return NULL;
    }
    //至此两个链均带环,考虑三种情况
    //先取出环入口
    cur1 = GetCycleEntrance(pHead1, cur1);
    cur2 = GetCycleEntrance(pHead1, cur2);
    traversal = cur1->_pNext;
    //情况3:相交且环入口相同
    if (cur1 == cur2)
    {
        return cur1;
    }
    //情况4:带环相交入口不同
    //情况5:带环不相交
    while (1)
    {
        traversal = traversal->_pNext;
        if (traversal == cur2)//该种情况为情况4,打印两个地址返回其中一个指针
        {
            printf("#%p\n#%p\n", cur1, cur2);
            return cur1;
        }
        if (traversal == cur1)//带环不想交,返回空
        {
            return NULL;
        }
    }
}

14.复杂链表的复制

解释一下题目,每个节点有两个指针相当于单链表的基础上多一个random指针,这个指针可以指向任意节点或为空,要求复制该链表
直接复制非常困难,思路是在原链表的基础上每个节点后复制相同节点,如此复制,random节点就可以直接复制目标rando的next,再断开节点拆分成两个链表实现复制
如果这段话理解有困难可以看图理解

欢迎访问jo_qzy的博客

typedef struct SCListNode
{
    DataType _data;
    struct SCListNode* _next;
    struct SCListNode* _random;
}SCListNode;

SCListNode* CopyComplexList(SCListNode* pHead)
{
    SCListNode* cur = pHead;
    SCListNode* next = NULL;
    SCListNode* newnode = NULL;
    SCListNode* newhead = NULL;
    while (cur)
    {
        next = cur->_next;
        cur->_next = (SCListNode*)malloc(sizeof(SCListNode));
        cur->_next->_next = next;
        cur = next;
    }
    cur = pHead;
    newhead = cur->_next;
    while (cur)
    {
        newnode = cur->_next;
        newnode->_random = cur->_random->_next;
        cur = cur->_next->_next;
    }
    cur = pHead;
    while (cur)
    {
        newnode = cur->_next;
        cur->_next = newnode->_next;
        cur = newnode->_next;
        if (newnode->_next == NULL)
        {
            continue;
        }
        newnode->_next = cur->_next;
    }
    return newhead;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值